转载

源码分析-ConcurrentLinkedQueue

一.序言

现在并发操作中都要求高效,都在想怎么去掉直接加锁带来的线程切换的开销,这里分享自己对concurrentLinkedQueue  的部分代码的理解,看看他无锁的原因,了解大神的设计思路。

关于 它的工作流程 参考JDK1.6 :http://ifeve.com/concurrentlinkedqueue/

本文分析基于JDK 1.7.0_79

二.源码分析

1.介绍:concurrentlinkedqueue 设计有head  和 tail 两个节点,以及节点类 Node,主要看Node 部分

private static class Node<E> {  // Node 里面包含了 item 节点值  以及 下一个节点 next  // item 和 next 都是valatile  可见性保证了  volatile E item;  volatile Node<E> next;  private static final sun.misc.Unsafe UNSAFE;  // 并且初始化的时候 就会获得item 和 next 的偏移量  // 这为后面的cas 做了准备,如何使用继续看下面  private static final long itemOffset;  private static final long nextOffset;  static {      try {   UNSAFE = sun.misc.Unsafe.getUnsafe();   Class k = Node.class;   itemOffset = UNSAFE.objectFieldOffset       (k.getDeclaredField("item"));   nextOffset = UNSAFE.objectFieldOffset       (k.getDeclaredField("next"));      } catch (Exception e) {   throw new Error(e);      }  }     } } 

2.从常规的 offer 方法进入,因为并发的主要也是offer 和 remove 上的竞争。

public boolean offer(E e) {  checkNotNull(e);// 检查,为空直接异常  // 创建新节点,并将e 作为节点的item  final Node<E> newNode = new Node<E>(e);  // 这里操作比较多,将尾节点tail 赋给变量 t,p  for (Node<E> t = tail, p = t;;) {   // 并获取q 也就是 tail 的下一个节点   Node<E> q = p.next;   // 如果下一个节点是null,说明tail 是处于尾节点上   if (q == null) {    // 然后用cas 将下一个节点设置成为新节点    // 这里用cas 操作,如果多线程的情况,总会有一个先执行成功,失败的线程继续执行循环。    // 关于casNext 的分析,跳转 1.1    // <A>     if (p.casNext(null, newNode)) {     // 如果p.casNext有个线程成功了,p=newNode      // 比较 t (tail) 是不是 最后一个节点     if (p != t)       // 如果不等,就利用cas将,尾节点移到最后      // 如果失败了,那么说明有其他线程已经把tail移动过,也是OK的      <B>      casTail(t, newNode);       return true;    }    // 如果<A>失败了,说明肯定有个线程成功了,    // 这时候失败的线程,又会执行for 循环,再次设值,直到成功。   }   else if (p == q)     // 有可能刚好插入一个,然后P 就被删除了,那么 p==q    // 这时候在头结点需要从新定位。    p = (t != (t = tail)) ? t : head;   else    // 这里是为了当P不是尾节点的时候,将P 移到尾节点,方便下一次插入    // 也就是一直保持向前推进    p = (p != t && t != (t = tail)) ? t : q;  } } 

1.1 casNext 分析

// 对应上面的 Node<E> q = p.next;p.casNext(null,newNode) // 他是一个Node 内的方法, boolean casNext(Node<E> cmp, Node<E> val) {             // 可以看到,它是用p.next (null) 未偏移量,设置新值的             // cas 是可以根据内存中的偏移量 改变值,详细这里不解释             return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val); } // 既然是可以并发执行,那么当多个线程同一时间执行到这里的时候,必然只有1个 成功,后面的都失// 败。关于成功和失败的处理 继续回到上面 1

3.poll 方法解释

public E poll() {  // 设置起始点  restartFromHead:  for (;;) {   for (Node<E> h = head, p = h, q;;) {    E item = p.item;    // 利用cas 将第一个节点,设置未null    if (item != null && p.casItem(item, null)) {     // 和上面类似,p的next被删了,     // 然后然后判断一下,目的为了保证head的next不为空     if (p != h) // hop two nodes at a time      updateHead(h, ((q = p.next) != null) ? q : p);     return item;    }    else if ((q = p.next) == null) {     // 有可能已经被另外线程先删除了下一个节点     // 那么需要先设定head 的位置,并返回null     updateHead(h, p);     return null;    }    else if (p == q)     // 这个一般是删完了(有点模糊)     continue restartFromHead;    else     // 和offer 类似,这历使用保证下一个节点有值,才能删除     p = q;   }  } } 

4.remove 方法

public boolean remove(Object o) {  if (o == null) return false;  Node<E> pred = null;  // 这里是从head 开始的,中间还涉及到head 的判断等从操作  // 跟一般for 循环类似,要遍历的- -,这样的操作o 很靠后的时候,会慢- -!  // - -不太喜欢这方法,了解作用  for (Node<E> p = first(); p != null; p = succ(p)) {   E item = p.item;   if (item != null &&     o.equals(item) &&     p.casItem(item, null)) {    Node<E> next = succ(p);    if (pred != null && next != null)     pred.casNext(p, next);    return true;   }   pred = p;  }  return false; } 

5.size 分析

public int size() {  int count = 0;  // 很纠结的是,这里依然用循环获取,判断节点是否有值。然后累加  // 比较伤,可能作者是考虑offer poll 等操作开始计算,难以控制,用这种原始的方法  // 比较伤- -,做为了解  for (Node<E> p = first(); p != null; p = succ(p))   if (p.item != null){    // Collection.size() spec says to max out    if (++count == Integer.MAX_VALUE)     break;   }  return count; } 

6.contains 方法

// 这个方法 也是用线性 遍历 比较的,也不多说了   public boolean contains(Object o) {   if (o == null) return false;   for (Node<E> p = first(); p != null; p = succ(p)) {    E item = p.item;    if (item != null && o.equals(item))     return true;   }   return false;  } 

三.小结

1. 这个东东,方法很多,就没一一分析了,主要是了解他的 构成和主要的代码逻辑,当然我没吃透他- -,毕竟写不出来,或者很好的该进,有问题还请指出啦,谢谢。

2.关于这个东东的应用,可能更倾向于offer 和poll 的高并发场景,而且你会发现每次都要创建node,cas 在竞争很激烈的情况,CPU会拉高,我觉得吧 可以采用,JDK1.8 Long 原子类增加的方法,多几个cell 减少cas 的问题。

3.像size,remove 等方法,这个我是比建议大量使用的,毕竟每个类的特点,不能照顾全部的优势,用就要用她的优势吧,其他地方的话,可以适当改写,当然~。~还是等 完全掌握了才行吧~。~

正文到此结束
Loading...