ConcurrentLinkedQueue
1. 引言
在并发编程中我们有时候需要使用线程安全的队列。如果我们要实现一个线程安全的队列有两种实现方式:一种是使用阻塞算法,另一种是使用非阻塞算法。使用阻塞算法的队列可以用一个锁(入队和出队用同一把锁)或两个锁(入队和出队用不同的锁)等方式来实现,而非阻塞的实现方式则可以使用循环CAS的方式来实现,本文让我们一起来研究下Doug Lea是如何使用非阻塞的方式来实现线程安全队列ConcurrentLinkedQueue的,相信从大师身上我们能学到不少并发编程的技巧。
2. ConcurrentLinkedQueue的介绍
ConcurrentLinkedQueue是一个基于链接节点的无界线程安全队列,它采用先进先出的规则对节点进行排序,当我们添加一个元素的时候,它会添加到队列的尾部,当我们获取一个元素时,它会返回队列头部的元素。它采用了“wait-free”算法来实现,该算法在Michael & Scott算法上进行了一些修改, Michael & Scott算法的详细信息可以参见参考资料一。
3. ConcurrentLinkedQueue的结构
我们通过ConcurrentLinkedQueue的类图来分析一下它的结构。
(图1)
ConcurrentLinkedQueue由head节点和tair节点组成,每个节点(Node)由节点元素(item)和指向下一个节点的引用(next)组成,节点与节点之间就是通过这个next关联起来,从而组成一张链表结构的队列。默认情况下head节点存储的元素为空,tair节点等于head节点。
private transient volatile Node<E> tail = head;
4. 入队列
入队列就是将入队节点添加到队列的尾部。为了方便理解入队时队列的变化,以及head节点和tair节点的变化,每添加一个节点我就做了一个队列的快照图。
(图二)
· 第一步添加元素1。队列更新head节点的next节点为元素1节点。又因为tail节点默认情况下等于head节点,所以它们的next节点都指向元素1节点。
· 第二步添加元素2。队列首先设置元素1节点的next节点为元素2节点,然后更新tail节点指向元素2节点。
· 第三步添加元素3,设置tail节点的next节点为元素3节点。
· 第四步添加元素4,设置元素3的next节点为元素4节点,然后将tail节点指向元素4节点。
通过debug入队过程并观察head节点和tail节点的变化,发现入队主要做两件事情,第一是将入队节点设置成当前队列尾节点的下一个节点。第二是更新tail节点,如果tail节点的next节点不为空,则将入队节点设置成tail节点,如果tail节点的next节点为空,则将入队节点设置成tail的next节点,所以tail节点不总是尾节点,理解这一点对于我们研究源码会非常有帮助。
上面的分析让我们从单线程入队的角度来理解入队过程,但是多个线程同时进行入队情况就变得更加复杂,因为可能会出现其他线程插队的情况。如果有一个线程正在入队,那么它必须先获取尾节点,然后设置尾节点的下一个节点为入队节点,但这时可能有另外一个线程插队了,那么队列的尾节点就会发生变化,这时当前线程要暂停入队操作,然后重新获取尾节点。让我们再通过源码来详细分析下它是如何使用CAS算法来入队的。
public boolean offer(E e) { if (e == null) throw new NullPointerException(); Node<E> n = new Node<E>(e, null); for (;;) {//轮询 Node<E> t = tail; //1. 临时变量指向tail节点 Node<E> s = t.getNext(); //2.临时变量指向tail节点的下一个节点 if (t == tail) { //3.由于采用的同步方式,没有加锁,轮询过程中其他线程可能改变tail的值,这里进行一下判断 if (s == null) { //4.如果tail的next为空,如果是单线程,一直都会走这一步,因为初始的head和tail的next必定为空。 if (t.casNext(s, n)) { //5.则先让tail的next指向新值n casTail(t, n); //6.然后让tail指向新值n return true;//7.退出轮询 } } else { //8.如果tail的next不为空,这种情况发生在:thread1,thread2 同时走到了步骤1,2,这时在thread1,和thread2中的s都为空,但是如果thread1先走到了步骤5,也就是把t的next设为了n,由于tail是volatile的,这时线程1走到步骤4后,往后走,tail的next相当于有值了,也就不会空了,就会走到步骤8,这时候说明已经把next赋值了,还没有修改tail的指针,于是帮这次工作完成,直接走步骤9,即把tail指向s,跟步骤6是一样的。 casTail(t, s); //9.让tail指向tail的next } } } } |
上边的源码提到了领个cast操作,这里介绍下AtomicReferenceFieldUpdater这个类的使用:
private static class Node<E> { private volatile E item; private volatile Node<E> next; //1. 定义了一个更新next指针的东东,他会通过反射获取一个目标方法的值 private static final AtomicReferenceFieldUpdater<Node, Node> nextUpdater = AtomicReferenceFieldUpdater.newUpdater (Node.class, Node.class, "next"); -------其他代码省略------- boolean casNext(Node<E> cmp, Node<E> val) { return nextUpdater.compareAndSet(this, cmp, val); //2.通过CAS操作更新 } AtomicReferenceFieldUpdaterImpl(Class<T> tclass, Class<V> vclass, String fieldName) { ----------省略构造方法的其他内容-------- offset = unsafe.objectFieldOffset(field); //3.生成这个field在内存中的偏移量 } public boolean compareAndSet(T obj, V expect, V update) { if (obj == null || obj.getClass() != tclass || cclass != null || (update != null && vclass != null && vclass != update.getClass())) updateCheck(obj, update); return unsafe.compareAndSwapObject(obj, offset, expect, update); //4.unsafe包会根据offset找到obj对应的field方法,然后通过反射执行obj的getFiled方法取得一个对象,然后和expect比较,如果相等,赋值update } 这段代码的执行步骤:以t.casNext(s, n)为例 1. 通过step1构造一个nextUpdater,这里会调用step4,在内存中记录一下需要更新的field,如“next”属性 2. 用户通过step2,调用nextUpdater的compareAndSet方法, 3. nextUpdater会调用unsafe的CAS方法,去内存地址找到需要反射的field名称,然后通过反射获取一个对象,如Object tmp = t.getNext(); 然后用tmp和expect比较,判断是否更新update值。 |
AtomicReferenceFieldUpdater类实例:
public static <U,W> AtomicReferenceFieldUpdater<U,W> newUpdater(Class<U> tclass,
Class<W> vclass,
String fieldName)
使用给定的字段为对象创建和返回一个更新器。需要 Class 参数检查反射类型和一般类型是否匹配。
tclass: 原始对象,如下例的Node对象
vclass: 通过域名称的get方法获取的返回值类型,如下例的getName()的返回值为String
fieldName:更新的域名称,如“name”
public class Node { private volatile String name; private volatile Node next; //定义一个nextupdater,三个参数:第一个是目标对象的class,第二个是目标方法的返回值类型,第三个是方法名(field名称) private static final AtomicReferenceFieldUpdater nextUpdater = AtomicReferenceFieldUpdater.newUpdater(Node.class, Node.class, "next"); private static final AtomicReferenceFieldUpdater<Node, String> nameUpdater = AtomicReferenceFieldUpdater.newUpdater(Node.class, String.class, "name"); public Node(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Node getNext() { return next; } public void setNext(Node next) { this.next = next; } /** * nextUpdater通过反射方法获取一个值,然后和expect进行比较,如果相等则把update赋值给this * @param expect * @param update * @return */ public boolean compareAndSetNext(Node expect, Node update) { return nextUpdater.compareAndSet(this, expect, update); } public boolean compareAndSetNamet(String expect, String update) { return nameUpdater.compareAndSet(this, expect, update); } } |
package com.mylearn.thread.atomic; /** * Created by IntelliJ IDEA. * User: yingkuohao * Date: 13-10-30 * Time: 上午10:12 * CopyRight:360buy * Descrption: * To change this template use File | Settings | File Templates. */ public class AtomicReferenceFieldUpdateTest { public static void main(String args[]) { AtomicReferenceFieldUpdateTest atomicReferenceFieldUpdateTest = new AtomicReferenceFieldUpdateTest(); // atomicReferenceFieldUpdateTest.testAtomicString(); // atomicReferenceFieldUpdateTest.testNode(); atomicReferenceFieldUpdateTest.testMyqueue(); } private void testNode() { Node node0 = new Node("head"); Node node1 = new Node("node1"); Node node2 = new Node("node2"); Node node3 = new Node("node3"); Node tail = node0; node0.setNext(node1); System.out.println(tail == node0); boolean bol = tail.compareAndSetNext(node1, node2); System.out.println("result =" + node0.getName()); } private void testMyqueue() { MyQueue myQueue = new MyQueue(); Node node0 = new Node("head"); Node node1 = new Node("node1"); myQueue.setHead(node0); myQueue.setTail(node0); //队列把 tail 指向node0 myQueue.getTail(node0, node1); //1.先用tailUpdater反射得到myQueue的tail,即node0 2. 通过CAS比较tail和node0,如果相等,则把tail赋值为node1. System.out.println(myQueue.getTail().getName()); } private void testAtomicString() { AtomicString atomicString = new AtomicString("abc"); atomicString.compareAndSetName("abc", "love"); System.out.println("atomicString=" + atomicString.getName()); } } |