基于CAS操作的无锁线程安全栈实现
import java.util.concurrent.atomic.AtomicReference;
class Node<T> {
T value;
Node<T> next;
Node(T value) {
this.value = value;
}
}
public class LockFreeStack<T> {
private AtomicReference<Node<T>> top = new AtomicReference<>();
public void push(T value) {
Node<T> newNode = new Node<>(value);
Node<T> oldTop;
do {
oldTop = top.get();
newNode.next = oldTop;
} while (!top.compareAndSet(oldTop, newNode));
}
public T pop() {
Node<T> oldTop;
Node<T> newTop;
do {
oldTop = top.get();
if (oldTop == null) {
return null;
}
newTop = oldTop.next;
} while (!top.compareAndSet(oldTop, newTop));
return oldTop.value;
}
}
ABA问题及解决方法
1. 版本号(Version Number)
- 检测方法:为每个节点添加一个版本号字段,每次节点状态变化时版本号递增。在进行CAS操作时,不仅比较节点的引用,还比较版本号。
- 解决方法:在节点类中添加一个
AtomicInteger
类型的版本号字段。例如:
class Node<T> {
T value;
Node<T> next;
AtomicInteger version = new AtomicInteger(0);
Node(T value) {
this.value = value;
}
}
- 优点:简单直接,能够有效检测和解决ABA问题。
- 缺点:增加了额外的空间开销,每次操作都需要更新版本号,增加了一定的时间开销。
2. 时间戳(Timestamp)
- 检测方法:与版本号类似,为每个节点记录一个时间戳,每次节点状态变化时更新时间戳。CAS操作时比较时间戳。
- 解决方法:在节点类中添加一个
long
类型的时间戳字段,并在每次节点状态变化时更新时间戳。例如:
class Node<T> {
T value;
Node<T> next;
long timestamp = System.currentTimeMillis();
Node(T value) {
this.value = value;
}
}
- 优点:能有效检测ABA问题,且时间戳具有唯一性,在分布式系统中也适用。
- 缺点:同样增加了空间开销,获取时间戳的操作可能有一定性能损耗,而且时间戳可能存在时钟回退等问题。
3. 原子标记(Atomic Mark)
- 检测方法:为节点添加一个标记字段,用于标记节点是否被删除或修改。在CAS操作时检查标记。
- 解决方法:在节点类中添加一个
AtomicBoolean
类型的标记字段,当节点被删除或修改时设置标记。例如:
class Node<T> {
T value;
Node<T> next;
AtomicBoolean isMarked = new AtomicBoolean(false);
Node(T value) {
this.value = value;
}
}
- 优点:空间开销相对较小,能检测到节点的关键状态变化。
- 缺点:只能检测节点是否被标记,无法精确跟踪节点变化的次数,可能存在误判情况。