我正在尝试使用java.util.concurrent并试图找出如何正确使用AtomicReference.compareAndSet来管理对单个共享状态单元的并发访问。
特别是:compareAndSet的以下用法是否正确且安全?有任何陷阱吗?
我的测试类是一个基于链接节点列表的简单堆栈。
public class LinkedStack<T> {
AtomicReference<Node<T>> topOfStack=new AtomicReference<Node<T>>();
public T push(T e) {
while(true) {
Node<T> oldTop=topOfStack.get();
Node<T> newTop=new Node<T>(e,oldTop);
if (topOfStack.compareAndSet(oldTop, newTop)) break;
}
return e;
}
public T pop() {
while(true) {
Node<T> oldTop=topOfStack.get();
if (oldTop==null) throw new EmptyStackException();
Node<T> newTop=oldTop.next;
if (topOfStack.compareAndSet(oldTop, newTop)) return oldTop.object;
}
}
private static final class Node<T> {
final T object;
final Node<T> next;
private Node (T object, Node<T> next) {
this.object=object;
this.next=next;
}
}
...................
}
答案 0 :(得分:5)
是的,这正是应该如何使用的。
也许以下语法会更优雅:
Node<T> oldTop = null;
Node<T> newTop = null;
do {
oldTop=topOfStack.get();
newTop=new Node<T>(e,oldTop);
} while (!topOfStack.compareAndSet(oldTop, newTop));
答案 1 :(得分:4)
目前的形式是正确的。
您的结构称为Treiber的堆栈。它是一个简单的线程安全的无锁结构,只有单点争用(topOfStack
),因此往往会严重扩展(在争用中它会捶打,并且与MMU不能很好地兼容) )。如果争用可能很低但仍然需要线程安全,那么这是一个很好的选择。
有关缩放堆栈算法的进一步阅读,请参阅Danny Hendler,Nir Shavit和Lena Yerushalmi撰写的“A Scalable Lock-free Stack Algorithm (pdf)”。
答案 2 :(得分:3)
这一切看起来都不错(axtavt所说的并不新鲜),我认为值得注意的一件事是弹出或推出失败的速度现在是ConcurrentLinkedQueue的两倍。当我说失败时,我只是意味着你必须再次在while循环中执行,假设另一个线程在你之前弹出或推送。
虽然这可能超出了您想要实现的范围,但某种退避协议将有助于在高争用情况下的性能。