ABA 问题示例
假设我们有一个简单的栈实现,用 CAS(Compare and Swap)来实现无锁出栈操作:
import java.util.concurrent.atomic.AtomicReference;
class Node {
int value;
Node next;
Node(int value) {
this.value = value;
}
}
class Stack {
AtomicReference<Node> head = new AtomicReference<>();
void push(int value) {
Node newNode = new Node(value);
Node oldHead;
do {
oldHead = head.get();
newNode.next = oldHead;
} while (!head.compareAndSet(oldHead, newNode));
}
int pop() {
Node oldHead;
Node newHead;
do {
oldHead = head.get();
if (oldHead == null) {
throw new RuntimeException("Stack is empty");
}
newHead = oldHead.next;
} while (!head.compareAndSet(oldHead, newHead));
return oldHead.value;
}
}
public class ABADemo {
public static void main(String[] args) {
Stack stack = new Stack();
stack.push(1);
stack.push(2);
// Simulate ABA problem
new Thread(() -> {
int value = stack.pop(); // This should pop 2
System.out.println("Popped: " + value);
stack.push(2); // Push 2 back, creating ABA scenario
}).start();
new Thread(() -> {
stack.pop(); // Incorrect assumptions due to ABA
}).start();
}
}
解决 ABA 问题
可以通过增加版本号来解决 ABA 问题:
import java.util.concurrent.atomic.AtomicStampedReference;
class StackWithVersion {
AtomicStampedReference<Node> head = new AtomicStampedReference<>(null, 0);
void push(int value) {
Node newNode = new Node(value);
int[] stampHolder = new int[1];
Node oldHead;
do {
oldHead = head.get(stampHolder);
newNode.next = oldHead;
} while (!head.compareAndSet(oldHead, newNode, stampHolder[0], stampHolder[0] + 1));
}
int pop() {
int[] stampHolder = new int[1];
Node oldHead;
Node newHead;
do {
oldHead = head.get(stampHolder);
if (oldHead == null) {
throw new RuntimeException("Stack is empty");
}
newHead = oldHead.next;
} while (!head.compareAndSet(oldHead, newHead, stampHolder[0], stampHolder[0] + 1));
return oldHead.value;
}
}
public class ABAPreventionDemo {
public static void main(String[] args) {
StackWithVersion stack = new StackWithVersion();
stack.push(1);
stack.push(2);
// Simulate ABA prevention
new Thread(() -> {
int value = stack.pop(); // This should pop 2
System.out.println("Popped: " + value);
stack.push(2); // Push 2 back
}).start();
new Thread(() -> {
stack.pop(); // Correctly handled due to versioning
}).start();
}
}
在这个解决方案中,我们使用 AtomicStampedReference
来同时管理节点和版本号,确保即使值相同,版本号的变化也能被检测到。