并发链接队列使用CAS

时间:2018-12-25 11:15:27

标签: java multithreading thread-safety

我在IBM Developer中找到了有关并发链接队列的代码段。

但是我无法理解其中的一部分。

while(true){
    ...
    if(curTail == Tail.get()){
        if(residue == null){
            ...
        }
    }
}

根据curTail和残渣的定义,我认为curTail是Tail的副本,而curTail是等于Tail.next的指针。

我担心函数compareAndSet将判断调用者对象是否等于第一个参数,为什么在调用此函数之前必须判断它们?我认为下面的代码可以使同样的事情变得很好。

        while (true) {
            Node<E> curTail = tail.get();
            Node<E> residue = curTail.next.get();
                if (curTail.next.compareAndSet(null, newNode)) /* C */ {
                    tail.compareAndSet(curTail, newNode) /* D */ ;
                    return true;
                } else {
                    tail.compareAndSet(curTail, residue) /* B */;
                }
            }
        }

任何帮助将不胜感激。 谢谢。

public class LinkedQueue <E> {
    private static class Node <E> {
        final E item;
        final AtomicReference<Node<E>> next;
        Node(E item, Node<E> next) {
            this.item = item;
            this.next = new AtomicReference<Node<E>>(next);
        }
    }
    private AtomicReference<Node<E>> head
        = new AtomicReference<Node<E>>(new Node<E>(null, null));
    private AtomicReference<Node<E>> tail = head;
    public boolean put(E item) {
        Node<E> newNode = new Node<E>(item, null);
        while (true) {
            Node<E> curTail = tail.get();
            Node<E> residue = curTail.next.get();
            if (curTail == tail.get()) {
                if (residue == null) /* A */ {
                    if (curTail.next.compareAndSet(null, newNode)) /* C */ {
                        tail.compareAndSet(curTail, newNode) /* D */ ;
                        return true;
                    }
                } else {
                    tail.compareAndSet(curTail, residue) /* B */;
                }
            }
        }
    }
}

1 个答案:

答案 0 :(得分:1)

作为参考:例如,该算法的代码说明可在this page from the IBM Developer website上找到。

上面链接的页面将为您提供每个操作的目的 A B C D ,以及为什么要求它们允许在同时更新队列尾部的线程之间允许建设性干扰

您的更改破坏了算法。如果 C 不成功,则必须执行else子句。相反,它扮演的角色是当一个线程截获另一个线程未完成的尾部[*]更新时,重现操作 D

[*]:在 C 之后但在 D 之前。

要了解失败的原因和时间,请考虑以下情形。

while (true) {
   Node<E> curTail = tail.get();
   Node<E> residue = curTail.next.get();

   /* (i) */

   if (curTail.next.compareAndSet(null, newNode)) /* C */ {
     tail.compareAndSet(curTail, newNode) /* D */ ;
     return true;
   } else {
      tail.compareAndSet(curTail, residue) /* B */;
   }
}
  • 两个线程 T1 T2 同时从位置(i)开始。它们的堆栈具有对curTailresidue的相同引用。 residue应该为null(即队列应该处于静止状态)。

  • T1 成功完成第一个CAS C 。它尚未执行 D

  • T2 导致CAS C 失败,输入else,成功执行CAS B ,因为引用{ {1}}不变。

  • T1 使CAS D 失败,因为 C已将分配给tail的引用设置为tail 。没有后备,方法退出。没有插入 T2 中的元素。

  • 第二次尝试 T1 ,分配给null的引用为tail,而null抛出curTail.next。数据结构已损坏。

总而言之, A B 是成对使用的。它们的存在是为了确保干扰线程可以帮助队列收敛到 normal 状态并从处于 intermediate 状态的队列中恢复。想象一个线程执行 C ,但是在有机会运行 D 之前被杀死。没有 A B ,队列将永远被破坏。 A B 确保可以重建状态并完成未完成的插入。