ConcurrentLinkedQueue

ConcurrentLinkedQueue

1. 引言

在并发编程中我们有时候需要使用线程安全的队列。如果我们要实现一个线程安全的队列有两种实现方式:一种是使用阻塞算法,另一种是使用非阻塞算法。使用阻塞算法的队列可以用一个锁(入队和出队用同一把锁)或两个锁(入队和出队用不同的锁)等方式来实现,而非阻塞的实现方式则可以使用循环CAS的方式来实现,本文让我们一起来研究下Doug Lea是如何使用非阻塞的方式来实现线程安全队列ConcurrentLinkedQueue的,相信从大师身上我们能学到不少并发编程的技巧。

2. ConcurrentLinkedQueue的介绍

ConcurrentLinkedQueue是一个基于链接节点的无界线程安全队列,它采用先进先出的规则对节点进行排序,当我们添加一个元素的时候,它会添加到队列的尾部,当我们获取一个元素时,它会返回队列头部的元素。它采用了waitfree算法来实现,该算法在Michael & Scott算法上进行了一些修改, Michael & Scott算法的详细信息可以参见参考资料一

3. ConcurrentLinkedQueue的结构

我们通过ConcurrentLinkedQueue的类图来分析一下它的结构。

(图1

ConcurrentLinkedQueuehead节点和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,设置元素3next节点为元素4节点,然后将tail节点指向元素4节点。

通过debug入队过程并观察head节点和tail节点的变化,发现入队主要做两件事情,第一是将入队节点设置成当前队列尾节点的下一个节点。第二是更新tail节点,如果tail节点的next节点不为空,则将入队节点设置成tail节点,如果tail节点的next节点为空,则将入队节点设置成tailnext节点,所以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.如果tailnext为空,如果是单线程,一直都会走这一步,因为初始的headtailnext必定为空。

                    if (t.casNext(s, n)) { //5.则先让tailnext指向新值n

                        casTail(t, n); //6.然后让tail指向新值n

                        return true;//7.退出轮询

                    }

                } else { //8.如果tailnext不为空,这种情况发生在:thread1thread2 同时走到了步骤1,2,这时在thread1,thread2中的s都为空,但是如果thread1先走到了步骤5,也就是把tnext设为了n,由于tailvolatile的,这时线程1走到步骤4后,往后走,tailnext相当于有值了,也就不会空了,就会走到步骤8,这时候说明已经把next赋值了,还没有修改tail的指针,于是帮这次工作完成,直接走步骤9,即把tail指向s,跟步骤6是一样的。

                    casTail(t, s); //9.tail指向tailnext 

                }

            }

        }

    }

上边的源码提到了领个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方法,然后通过反射执行objgetFiled方法取得一个对象,然后和expect比较,如果相等,赋值update

        }

这段代码的执行步骤:以t.casNext(s, n)为例

1. 通过step1构造一个nextUpdater,这里会调用step4,在内存中记录一下需要更新的field,如“next”属性 

2. 用户通过step2,调用nextUpdater的compareAndSet方法,

3. nextUpdater会调用unsafeCAS方法,去内存地址找到需要反射的field名称,然后通过反射获取一个对象,如Object tmp = t.getNext(); 然后用tmpexpect比较,判断是否更新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反射得到myQueuetail,即node0 2. 通过CAS比较tailnode0,如果相等,则把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());

    }

}

转:http://www.infoq.com/cn/articles/ConcurrentLinkedQueue

http://www.2cto.com/kf/201108/98489.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值