正确使用AtomicReference.compareAndSet进行堆栈实现

时间:2011-04-05 14:20:08

标签: java concurrency atomic

我正在尝试使用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;
    }
  } 
  ...................
}

3 个答案:

答案 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循环中执行,假设另一个线程在你之前弹出或推送。

虽然这可能超出了您想要实现的范围,但某种退避协议将有助于在高争用情况下的性能。