CAS中的ABA问题

什么是ABA问题

ABA就是有两个线程共享一个变量value,A线程读取到的时候这个值为1,准备修改的时候值还是1,但是可能这过程中B线程修改了变量1->2->1,仅仅用CAS无法得知这种修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import java.util.concurrent.atomic.AtomicInteger;

public class ABAProblemExample {

private static AtomicInteger atomicValue = new AtomicInteger(1);

public static void main(String[] args) throws InterruptedException {

Thread threadA = new Thread(() -> {
int expectedValue = atomicValue.get();
System.out.println("Thread-A reads value: " + expectedValue);

// 模拟线程 B 修改了值
try {
Thread.sleep(1000); // 休眠让线程 B 先执行
} catch (InterruptedException e) {
e.printStackTrace();
}

boolean isSuccess = atomicValue.compareAndSet(expectedValue, 2);
System.out.println("Thread-A CAS result: " + isSuccess + ", new value: " + atomicValue.get());
});

Thread threadB = new Thread(() -> {
try {
Thread.sleep(500); // 确保 Thread-A 先读取到值
} catch (InterruptedException e) {
e.printStackTrace();
}

// 模拟 ABA 问题,先把值改成 B 然后再改回 A
atomicValue.compareAndSet(1, 3); // A -> B
System.out.println("Thread-B changes value to: " + atomicValue.get());

atomicValue.compareAndSet(3, 1); // B -> A
System.out.println("Thread-B changes value back to: " + atomicValue.get());
});

threadA.start();
threadB.start();

threadA.join();
threadB.join();
}
}

上面这个程序会输出

1
2
3
4
Thread-A reads value: 1
Thread-B changes value to: 3
Thread-B changes value back to: 1
Thread-A CAS result: true, new value: 2

使用AtomicStampedReference解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import java.util.concurrent.atomic.AtomicStampedReference;

public class ABAProblemSolution {

private static AtomicStampedReference<Integer> atomicStampedRef = new AtomicStampedReference<>(1, 0);

public static void main(String[] args) throws InterruptedException {

Thread threadA = new Thread(() -> {
int[] stampHolder = new int[1];
Integer expectedValue = atomicStampedRef.get(stampHolder);
int currentStamp = stampHolder[0];
System.out.println("Thread-A reads value: " + expectedValue + " with stamp: " + currentStamp);

// 模拟线程 B 修改了值
try {
Thread.sleep(1000); // 休眠让线程 B 先执行
} catch (InterruptedException e) {
e.printStackTrace();
}

boolean isSuccess = atomicStampedRef.compareAndSet(expectedValue, 2, currentStamp, currentStamp + 1);
System.out.println("Thread-A CAS result: " + isSuccess + ", new value: " + atomicStampedRef.getReference() + " with stamp: " + atomicStampedRef.getStamp());
});

Thread threadB = new Thread(() -> {
try {
Thread.sleep(500); // 确保 Thread-A 先读取到值
} catch (InterruptedException e) {
e.printStackTrace();
}

// 模拟 ABA 问题,先把值改成 B 然后再改回 A
int currentStamp = atomicStampedRef.getStamp();
atomicStampedRef.compareAndSet(1, 3, currentStamp, currentStamp + 1); // A -> B
System.out.println("Thread-B changes value to: " + atomicStampedRef.getReference() + " with stamp: " + atomicStampedRef.getStamp());

atomicStampedRef.compareAndSet(3, 1, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1); // B -> A
System.out.println("Thread-B changes value back to: " + atomicStampedRef.getReference() + " with stamp: " + atomicStampedRef.getStamp());
});

threadA.start();
threadB.start();

threadA.join();
threadB.join();
}
}

上面的程序会输出

1
2
3
4
Thread-A reads value: 1 with stamp: 0
Thread-B changes value to: 3 with stamp: 1
Thread-B changes value back to: 1 with stamp: 2
Thread-A CAS result: false, new value: 1 with stamp: 2

原因是AtomicStampedReference的CAS操作需要比较版本号,如果期待的版本号不等,则CAS失败

1
2
3
4
5
6
7
8
9
10
11
12
public boolean compareAndSet(V   expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}

CAS中的ABA问题
http://hhubibi.github.io/2024/09/06/ABA/
作者
hhubibi
发布于
2024年9月6日
许可协议