源码分析之Queue(三)ConcurrentLinkedQueue
ConcurrentLinkedQueue是一个基于链接节点的无界线程安全队列,遵循队列的FIFO原则,队尾入队,队首出队。其非阻塞的方式使用自旋CAS(Compare and swap,即比较并交换)的方式来实现。
ConcurrentLinkedQueue 由 head 节点和 tail 节点组成,每个节点(Node)由节点元素(item)和指向下一个节点(next)的引用组成,节点与节点之间就是通过这个 next 关联起来,从而组成一张链表结构的队列。
ConcurrentLinkedQueue 的非阻塞算法实现主要可概括为下面几点:
- 使用 CAS 原子指令来处理对数据的并发访问,这是非阻塞算法得以实现的基础。
- head/tail 并非总是指向队列的头 / 尾节点,也就是说允许队列处于不一致状态。 这个特性把入队 /出队时,原本需要一起原子化执行的两个步骤分离开来,从而缩小了入队 /出队时需要原子化更新值的范围到唯一变量。这是非阻塞算法得以实现的关键。
- 以批处理方式来更新head/tail,从整体上减少入队 / 出队操作的开销。
源码解析
public class ConcurrentLinkedQueue<E> extends AbstractQueue<E> implements Queue<E>, java.io.Serializable { private static final long serialVersionUID = 196745693267521676L; private static class Node<E> { //volatile 保证变量修改的线程可见性 volatile E item; volatile Node<E> next; /** * Constructs a new node. Uses relaxed write because item can only be seen after publication via casNext. */ Node(E item) { UNSAFE.putObject(this, itemOffset, item); } // 如果this的item等于cmp,则修改为val。这是一个原子操作,一定线程安全 boolean casItem(E cmp, E val) { return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val); } // 设置this的next属性为val,虽然是原子操作,但当两个线程都获取到当前的this的时候,同时赋值会导致线程不安全 void lazySetNext(Node<E> val) { UNSAFE.putOrderedObject(this, nextOffset, val); } // 如果this的next等于cmp,则修改为val boolean casNext(Node<E> cmp, Node<E> val) { return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val); } // 通过下面的静态方法,获取Unsafe的实例 private static final sun.misc.Unsafe UNSAFE; 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); } } } /** * A node from which the first live (non-deleted) node (if any) can be reached in O(1) time. */ private transient volatile Node<E> head; /** * A node from which the last node on list (that is, the unique node with node.next == null) can be reached in O(1) time. */ private transient volatile Node<E> tail; /** * Creates a {@code ConcurrentLinkedQueue} that is initially empty. */ public ConcurrentLinkedQueue() { head = tail = new Node<E>(null); } /** * Creates a {@code ConcurrentLinkedQueue} initially containing the elements of the given collection, * added in traversal order of the collection's iterator. * 按照集合的顺序将集合所有元素put到队列中。 * @param c the collection of elements to initially contain * @throws NullPointerException if the specified collection or any of its elements are null */ public ConcurrentLinkedQueue(Collection<? extends E> c) { Node<E> h = null, t = null; for (E e : c) { checkNotNull(e); Node<E> newNode = new Node<E>(e); if (h == null) h = t = newNode; else { t.lazySetNext(newNode); t = newNode; } } if (h == null) h = t = new Node<E>(null); head = h; tail = t; } // Have to override just to update the javadoc /** * Inserts the specified element at the tail of this queue. * @return {@code true} (as specified by {@link Collection#add}) * @throws NullPointerException if the specified element is null */ public boolean add(E e) { return offer(e); } /** * Tries to CAS head to p. If successful, repoint old head to itself * as sentinel for succ(), below. 更新头节点。原子更新h为p成功后,延迟更新h的next为他自己 */ final void updateHead(Node<E> h, Node<E> p) { if (h != p && casHead(h, p)) h.lazySetNext(h); } /** * Returns the successor of p, or the head node if p.next has been * linked to self, which will only be true if traversing with a * stale pointer that is now off the list. * 获取当前节点的next元素,如果是自引入节点则返回真正的头节点 */ final Node<E> succ(Node<E> p) { Node<E> next = p.next; return (p == next) ? head : next; } /** * Inserts the specified element at the tail of this queue. * @return {@code true} (as specified by {@link Queue#offer}) * @throws NullPointerException if the specified element is null */ public boolean offer(E e) { checkNotNull(e); final Node<E> newNode = new Node<E>(e); //循环CSA设置插入节点 for (Node<E> t = tail, p = t;;) { Node<E> q = p.next; if (q == null) { // p is last node if (p.casNext(null, newNode)) { //t是当前线程读到的tail快照,p是上面CAS时队列中的最后一个元素。两者不一致说明需要更新tail了 if (p != t) // hop two nodes at a time casTail(t, newNode); // Failure is OK. cas失败说明其他线程更新过了 return true; } // Lost CAS race to another thread; re-read next } else if (p == q) //对于已经移除的元素,会将next置为本身,用于判断当前元素已经出队 //重新读取一次tail到快照t,如果t未发生变化,就从head继续下去;否则就让p从新的t开始继续尝试入队 p = (t != (t = tail)) ? t : head; else // Check for tail updates after two hops. p = (p != t && t != (t = tail)) ? t : q; } } public E poll() { restartFromHead://从head开始死循环 for (;;) { for (Node<E> h = head, p = h, q;;) { E item = p.item; //CAS设置item值为null if (item != null && p.casItem(item, null)) { // Successful CAS is the linearization point for item to be removed from this queue. 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) {//p节点的下一节点为null,则表明队列已经空了 updateHead(h, p); return null; } else if (p == q) //说明p已经出队了 continue restartFromHead; else p = q; } } } public E peek() { restartFromHead: for (;;) { for (Node<E> h = head, p = h, q;;) { E item = p.item; if (item != null || (q = p.next) == null) {//节点item不为空或者空队列 updateHead(h, p); return item; } else if (p == q) continue restartFromHead; else p = q; } } } /** * Returns the first live (non-deleted) node on list, or null if none. */ Node<E> first() { restartFromHead: for (;;) { for (Node<E> h = head, p = h, q;;) { boolean hasItem = (p.item != null); if (hasItem || (q = p.next) == null) { updateHead(h, p); return hasItem ? p : null; } else if (p == q) continue restartFromHead; else p = q; } } } /** * @return {@code true} if this queue contains no elements */ public boolean isEmpty() { return first() == null; } /** * Returns the number of elements in this queue. * 只是粗略估计,不能保证统计过程中是否有其他线程修改队列*/ public int size() { int count = 0; 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; } /** * Returns {@code true} if this queue contains the specified element*/ 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; } /** * Removes a single instance of the specified element from this queue * @return {@code true} if this queue changed as a result of the call */ public boolean remove(Object o) { if (o != null) { Node<E> next, pred = null; for (Node<E> p = first(); p != null; pred = p, p = next) { boolean removed = false; E item = p.item; if (item != null) { if (!o.equals(item)) { next = succ(p); continue; } removed = p.casItem(item, null); } next = succ(p); if (pred != null && next != null) // unlink pred.casNext(p, next); if (removed) return true; } } return false; } /** * Appends all of the elements in the specified collection to the end of * this queue, in the order that they are returned by the specified * collection's iterator*/ public boolean addAll(Collection<? extends E> c) { if (c == this) // As historically specified in AbstractQueue#addAll throw new IllegalArgumentException(); // Copy c into a private chain of Nodes Node<E> beginningOfTheEnd = null, last = null; for (E e : c) { checkNotNull(e); Node<E> newNode = new Node<E>(e); if (beginningOfTheEnd == null) beginningOfTheEnd = last = newNode; else { last.lazySetNext(newNode); last = newNode; } } if (beginningOfTheEnd == null) return false; // Atomically append the chain at the tail of this collection for (Node<E> t = tail, p = t;;) { Node<E> q = p.next; if (q == null) { // p is last node if (p.casNext(null, beginningOfTheEnd)) { // Successful CAS is the linearization point // for all elements to be added to this queue. if (!casTail(t, last)) { // Try a little harder to update tail, // since we may be adding many elements. t = tail; if (last.next == null) casTail(t, last); } return true; } // Lost CAS race to another thread; re-read next } else if (p == q) // We have fallen off list. If tail is unchanged, it // will also be off-list, in which case we need to // jump to head, from which all live nodes are always // reachable. Else the new tail is a better bet. p = (t != (t = tail)) ? t : head; else // Check for tail updates after two hops. p = (p != t && t != (t = tail)) ? t : q; } } /** * Returns an array containing all of the elements in this queue, in proper sequence. * @return an array containing all of the elements in this queue */ public Object[] toArray() { // Use ArrayList to deal with resizing. ArrayList<E> al = new ArrayList<E>(); for (Node<E> p = first(); p != null; p = succ(p)) { E item = p.item; if (item != null) al.add(item); } return al.toArray(); } /** * Returns an array containing all of the elements in this queue*/ @SuppressWarnings("unchecked") public <T> T[] toArray(T[] a) { // try to use sent-in array int k = 0; Node<E> p; for (p = first(); p != null && k < a.length; p = succ(p)) { E item = p.item; if (item != null) a[k++] = (T)item; } if (p == null) { if (k < a.length) a[k] = null; return a; } // If won't fit, use ArrayList version ArrayList<E> al = new ArrayList<E>(); for (Node<E> q = first(); q != null; q = succ(q)) { E item = q.item; if (item != null) al.add(item); } return al.toArray(a); }/** * Throws NullPointerException if argument is null. * @param v the element */ private static void checkNotNull(Object v) { if (v == null) throw new NullPointerException(); } private boolean casTail(Node<E> cmp, Node<E> val) { return UNSAFE.compareAndSwapObject(this, tailOffset, cmp, val); } private boolean casHead(Node<E> cmp, Node<E> val) { return UNSAFE.compareAndSwapObject(this, headOffset, cmp, val); } // Unsafe mechanics private static final sun.misc.Unsafe UNSAFE; private static final long headOffset; private static final long tailOffset; static { try { UNSAFE = sun.misc.Unsafe.getUnsafe(); Class<?> k = ConcurrentLinkedQueue.class; headOffset = UNSAFE.objectFieldOffset (k.getDeclaredField("head")); tailOffset = UNSAFE.objectFieldOffset (k.getDeclaredField("tail")); } catch (Exception e) { throw new Error(e); } } }
总结:
- ConcurrentLinkedQueue是使用循环CAS的非阻塞无界先入先出的队列,性能高于阻塞队列。
- 通过同时跳两跳来设置head和tail,来减少对volatile变量的写操作(volatile变量的写操作需要刷新CPU缓存,影响性能)来优化性能。
- 新建一个Node节点,使用putObject方法,而不是使用volatile变量写的方式,用一次普通对象写换取volatile变量写的性能提升。
- offer、poll、peek、first、isEmpty方法的时间复杂度为O(1)
- size、remove方法的时间复杂度为O(n),而且是弱一致性的。
- iterator迭代器方法是弱一致性的。
- 不允许插入的元素为null。
- ConcurrentLinkedQueue不能用于线程池
为何 ConcurrentLinkedQueue 中需要遍历链表来获取 size 而不适用一个原子变量呢?
因为使用原子变量保存队列元素个数需要保证入队出队操作和操作原子变量是原子操作,而ConcurrentLinkedQueue 是使用 CAS 无锁算法的,所以无法做到这个。