1. CAS(Compare And Swap)导致的ABA问题
代码实例。
主要操作方法是 AtomicReference.compareAndSet(oldvalue, newValule)
同时使用了 CountDownLatch(类似计数器的功能)
(从日志也可以看出,线程的执行并不是按照我们创建或启动的顺序的)
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
public class AbaPro {
private static final Random RANDOM = new Random();
private static final String B = "B";
private static final String A = "A";
public static final AtomicReference<String> ATOMIC_REFERENCE = new AtomicReference<>(A);
public static void main(String[] args) throws InterruptedException {
final CountDownLatch startLatch = new CountDownLatch(1);
Thread[] threads = new Thread[20];
for (int i=0; i < 20; i++){
threads[i] = new Thread(){
@Override
public void run() {
String oldValue = ATOMIC_REFERENCE.get();
System.out.println(Thread.currentThread().getName()+ " oldValue: "+ oldValue);
try {
startLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ " 唤醒了 ");
try {
Thread.sleep(RANDOM.nextInt()&500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ " 等待一段时间后现在的值: "+ ATOMIC_REFERENCE.get());
// 1. 在这里: 第一次改 & 第三次改 : A -> B
if (ATOMIC_REFERENCE.compareAndSet(oldValue, B )){
System.out.println(Thread.currentThread().getName()+ " 已经对原始值进行了修改,此时值为: "+ ATOMIC_REFERENCE.get());
}
}
};
threads[i].start();
}
System.out.println(Thread.currentThread().getName() +" 即将 count down");
startLatch.countDown();
System.out.println(Thread.currentThread().getName() +" count down done");
Thread.sleep(200);
System.out.println(Thread.currentThread().getName() +" sleep 200ms done");
new Thread(){
@Override
public void run() {
try {
Thread.sleep(RANDOM.nextInt() & 200);
} catch (InterruptedException e) {
e.printStackTrace();
}
String oldVal = ATOMIC_REFERENCE.get();
// 2. 在这里: 第二次改 B -> A
while (!ATOMIC_REFERENCE.compareAndSet(ATOMIC_REFERENCE.get(), A));
System.out.println(Thread.currentThread().getName() +" 已经将值 "+oldVal+" 修改成原始值: A");
}
}.start();
}
}
日志打印:
Thread-0 oldValue: A
Thread-1 oldValue: A
Thread-2 oldValue: A
Thread-9 oldValue: A
Thread-13 oldValue: A
Thread-6 oldValue: A
Thread-8 oldValue: A
Thread-7 oldValue: A
Thread-12 oldValue: A
Thread-10 oldValue: A
Thread-5 oldValue: A
Thread-4 oldValue: A
Thread-3 oldValue: A
Thread-19 oldValue: A
Thread-15 oldValue: A
Thread-11 oldValue: A
Thread-16 oldValue: A
Thread-18 oldValue: A
Thread-17 oldValue: A
main 即将 count down
main count down done
Thread-2 唤醒了
Thread-14 oldValue: A
Thread-1 唤醒了
Thread-14 唤醒了
Thread-13 唤醒了
Thread-9 唤醒了
Thread-0 唤醒了
Thread-8 唤醒了
Thread-7 唤醒了
Thread-12 唤醒了
Thread-6 唤醒了
Thread-4 唤醒了
Thread-3 唤醒了
Thread-5 唤醒了
Thread-10 唤醒了
Thread-19 唤醒了
Thread-11 唤醒了
Thread-15 唤醒了
Thread-17 唤醒了
Thread-18 唤醒了
Thread-16 唤醒了
Thread-13 等待一段时间后现在的值: A
Thread-13 已经对原始值进行了修改,此时值为: B
Thread-12 等待一段时间后现在的值: B
Thread-8 等待一段时间后现在的值: B
Thread-5 等待一段时间后现在的值: B
Thread-6 等待一段时间后现在的值: B
Thread-7 等待一段时间后现在的值: B
Thread-3 等待一段时间后现在的值: B
Thread-1 等待一段时间后现在的值: B
main sleep 200ms done
Thread-14 等待一段时间后现在的值: B
Thread-18 等待一段时间后现在的值: B
Thread-0 等待一段时间后现在的值: B
Thread-15 等待一段时间后现在的值: B
Thread-4 等待一段时间后现在的值: B
Thread-2 等待一段时间后现在的值: B
Thread-20 已经将值 B 修改成原始值: A
Thread-10 等待一段时间后现在的值: A
Thread-10 已经对原始值进行了修改,此时值为: B
Thread-17 等待一段时间后现在的值: B
Thread-9 等待一段时间后现在的值: B
Thread-16 等待一段时间后现在的值: B
Thread-11 等待一段时间后现在的值: B
Thread-19 等待一段时间后现在的值: B
关键的日志在于:
Thread-13 已经对原始值进行了修改,此时值为: B
Thread-20 已经将值 B 修改成原始值: A
Thread-10 已经对原始值进行了修改,此时值为: B
2. 解决方案
java中提供了AtomicStampedReference来解决这个问题,它是基于版本或者是一种状态,在修改的过程中不仅对比值,也同时会对比版本号
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicStampedReference;
public class AbaProResolve {
private static final Random RANDOM = new Random();
private static final String B = "B";
private static final String A = "A";
private static final AtomicStampedReference<String> ATOMIC_STAMPED_REFERENCE = new AtomicStampedReference<>(A, 0);
public static void main(String[] args) throws InterruptedException {
final CountDownLatch startLatch = new CountDownLatch(1);
Thread[] threads = new Thread[20];
for (int i = 0; i < 20; i++) {
threads[i] = new Thread() {
@Override
public void run() {
String oldValue = ATOMIC_STAMPED_REFERENCE.getReference();
int stamp = ATOMIC_STAMPED_REFERENCE.getStamp();
try {
startLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(RANDOM.nextInt() & 500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ATOMIC_STAMPED_REFERENCE.compareAndSet(oldValue, B, stamp, stamp + 1)) {
System.out.println(Thread.currentThread().getName() + " 已经对原始值: " + oldValue + " 进行了修改,此时值为: " + ATOMIC_STAMPED_REFERENCE.getReference());
}
}
};
threads[i].start();
}
Thread.sleep(200);
startLatch.countDown();
new Thread() {
@Override
public void run() {
try {
Thread.sleep(RANDOM.nextInt() & 200);
} catch (InterruptedException e) {
e.printStackTrace();
}
int stamp = ATOMIC_STAMPED_REFERENCE.getStamp();
String oldVal = ATOMIC_STAMPED_REFERENCE.getReference();
while (!ATOMIC_STAMPED_REFERENCE.compareAndSet(B, A, stamp, stamp + 1)) {
stamp = ATOMIC_STAMPED_REFERENCE.getStamp();
}
System.out.println(Thread.currentThread().getName() + " 已经将值 " + oldVal + " 修改成原始值: A");
}
}.start();
}
}
日志打印: A -> B, B -> A
Thread-6 已经对原始值: A 进行了修改,此时值为: B
Thread-20 已经将值 B 修改成原始值: A
参考:
Java并发编程原理与实战四十三:CAS ---- ABA问题 - pony1223 - 博客园 (cnblogs.com)
网友评论