20171204_ConcurrentLinkedList源码的一些问题

来源:互联网 发布:董承非 知乎 编辑:程序博客网 时间:2024/05/29 09:30

今天在看ConcurrentLinkedList的1.8源码,结合网上的一些博客,发现有一些地方有坑,不是太好理解,就写下来总结一下吧。
首先,ConcurrentLinkedList是一个并发容器,它的并发性是通过CAS来实现的,这是实现非阻塞并发算法的基础。然后,head/tail 并非总是指向队列的头 / 尾节点,也就是说允许队列处于不一致状态这个特性把入队 / 出队时,原本需要一起原子化执行的两个步骤分离开来,从而缩小了入队 / 出队时需要原子化更新值的范围到唯一变量,这是非阻塞算法得以实现的关键。另外,下面的两点,很重要,这是理解算法的关键:

head的不变性和可变性条件

不变性:

1. 所有未删除节点,都能从head通过调用succ()方法遍历可达。
2. head不能为null。
3. head节点的next域不能引用到自身。

可变性:

1. head节点的item域可能为null,也可能不为null。
2. 允许tail滞后(lag behind)于head,也就是说:从head开始遍历队列,不一定能到达tail。

tail的不变性和可变性条件

不变性:

1. 通过tail调用succ()方法,最后节点总是可达的。
2. tail不能为null。

可变性:

1. tail节点的item域可能为null,也可能不为 null。
2. 允许tail滞后于head,也就是说:从head开始遍历队列,不一定能到达tail。
3. tail节点的next域可以引用到自身。

下面来看入队操作:

public boolean offer(E e) {     checkNotNull(e);     final Node<E> newNode = new Node<E>(e);     for (Node<E> t = tail, p = t;;)        {         Node<E> q = p.next;         if (q == null)                      {             if (p.casNext(null, newNode))              {                     if (p != t)        //1                     casTail(t, newNode);                    return true;             }         }         else if (p == q)   //2             p = (t != (t = tail)) ? t : head;          else   //3             p = (p != t && t != (t = tail)) ? t : q;     } }

在这我捡几个疑惑说:
1. if (p != t) 在单线程中是不需要考虑这个问题的,这个if判断一直为假。那么,在多线程中,这个判断什么时候为真呢?答案是在下面的else if,p可能会赋值为head,是不是会感到奇怪,在2中我会解释。p != t,说明此时tail没有更新,我们用casTail方法进行更新。在这里,casTail即使失败也没有关系,因为这说明有其他线程对tail更新了。
2. if (p == q) 什么情况下会出现,上面我们在tail的可变性上说了,tail.next可以指向自己,这表明tail现在所在指向的结点已被删除(从head遍历无法到达tail),那么就要从head开始遍历到所有的未删除结点。这就是我们要将p赋值为head的原因。
3. 剩下这种情况就是tail不是指向最后一个节点的时候,此时我们将p更新为q。

再来看出队操作:

public E poll(){      restartFromHead:      for (;;)       {          for (Node<E> h = head, p = h, q;;)           {              E item = p.item;              if (item != null && p.casItem(item, null))                  {                  if (p != h)       //1                      updateHead(h, ((q = p.next) != null) ? q : p);                  return item;              }              else if ((q = p.next) == null) //2              {                  updateHead(h, p);                  return null;              }              else if (p == q)  //3                  continue restartFromHead;              else  //4                  p = q;          }      }  }

看明白上面offer的几个疑点,相信这就不难解释了
1. 这和offer的1很相似,这时我们更新head节点。
2. head的item为null,而它next节点为null的时候,将head指向p这个伪节点,返回null。
3. p == q这个条件有点奇怪,应该是在4时候将q赋值给p,循环后另一个线程将q这个节点给删掉了,此时进入3
4. 在结点出队失败后可以保证下次尝试出队时p不为空(之前q = p.next != null才有可能跳到这一步)

原创粉丝点击