多线程并发编程11-ConcurrentLinkedQueue源码剖析

    今天来说一说ConcurrentLinkedQueue类,ConcurrentLinkedQueue类是线程安全的非阻塞无界的FIFIO队列,通过CAS算法进行入队和出队。

    ConcurrentLinkedQueue类中主要的成员变量如下:

private transient volatile Node head; //内部双向列表的头节点,volatile保证内存可见性。

private transient volatile Node tail; //内部双向列表的尾节点,volatile保证内存可见性。

private static final sun.misc.Unsafe UNSAFE;//UNSAFE实例用来执行CAS算法。

    下面对ConcurrentLinkedQueue类中常用方法源码进行介绍。

offer(E e)

    在队列尾部添加一个指定元素,由于队列是无界的,所以该方法一直会返回true。由于使用CAS算法,所有线程不会被阻塞挂起。

public boolean offer(E e) {

//(1)检查参数是否为null,是则抛出空指针异常

checkNotNull(e);

    final Node newNode =new Node(e);

    for (Node t =tail, p = t;;) {

Node q = p.next;

//(2)如果q为null,说明p是尾部节点,则进行插入操作

        if (q ==null) {

// (3)使用CAS算法设置p的next节点

            if (p.casNext(null, newNode)) {

//(4)CAS成功,则说明新增节点已经插入到链表,然后设置当前尾节点。

                if (p != t)

                    casTail(t, newNode);  // (5)尾结点的设置允许失败,不影响后序的操作。

                return true;

            }

        }

else if (p == q)

//(6)多线程操作时,由于poll操作移除元素后可能会把head变为自引用,也就是head的next还是head,这个时候需要重新找新的head

            p = (t != (t =tail)) ? t :head;

else

            // (7)寻找尾结点

            p = (p != t && t != (t =tail)) ? t : q;

    }

}

    由于CAS算法同一时间只能有一个线程修改成功,其他失败的线程会通过一次次循环尝试进行CAS操作,直到CAS成功才返回。相比于阻塞算法挂起调用线程,CAS算法以CPU资源换取上下文切换带来的开销。

add(E e)

public boolean add(E e) {

    return offer(e);

}

    add方法内部还是调用的offer方法,这里就不进行赘述了。

poll()

    在队列头部获取并移除一个元素,如果队列为空则返回null。

public E poll() {

    restartFromHead:

    for (;;) {

        for (Node h = head, p = h, q;;) {

            E item = p.item;

//(1)当前节点不为null,则将当前节点通过cas算法设置为null,cas操作成功则表示当前节点从链表中移除

            if (item != null && p.casItem(item, null)) {

                if (p != h) 

//(2)通过cas算法更新head节点。

                    updateHead(h, ((q = p.next) != null) ? q : p);

                return item;

            }

//(3)当前队列为空,返回null。

            else if ((q = p.next) == null) {

                updateHead(h, p);

                return null;

            }

//(4)如果当前节点被自引用,则重新寻找新的队列头节点

            else if (p == q)

                continue restartFromHead;

            else

                p = q;

        }

    }

}

    poll方法在移除一个元素是,只是简单地使用CAS操作当前节点的item值为null,然后通过重新设置头节点将该元素从队列里面移除,被移除的节点就成为了孤立的节点,这个节点会在垃圾回收时被回收。如果在执行分支中发现头节点被修改了,要跳到外层循环重新获取新的头节点。

peek()

    获取头部一个元素,并不移除,如果队列为空则返回null。

public E peek() {

    restartFromHead:

    for (;;) {

        for (Node h = head, p = h, q;;) {

            E item = p.item;

            if (item != null || (q = p.next) == null) {

                updateHead(h, p);

                return item;

            }

            else if (p == q)

                continue restartFromHead;

            else

                p = q;

        }

    }

}

    peek方法和poll方法差不多,只是前者没有调用casItem方法,将获取到的节点的item设置为null。

size()

    获取队列中的元素,是一个不精准的统计。

public int size() {

    int count = 0;

//(1)first()方法获取第一个非哨兵节点,没有则返回null

// succ()方法获取当前节点的next节点,如果是自引用节点则返回真正的头节点

    for (Node 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;

}

    由于ConcurrentLinkedQueue中都是使用的CAS没有加锁,而size方法统计的时候有可能其他的线程对ConcurrentLinkedQueue队列进行操作,从而导致统计元素的不准确性。

remove()

    如果队列中存在指定元素则删除该元素,如果存在多个则删除第一个并返回true,否则返回false。

public boolean remove(Object o) {

    if (o != null) {

        Node next, pred = null;

//(1)从head节点向tail节点进行遍历

        for (Node p = first(); p != null; pred = p, p = next) {

            boolean removed = false;

            E item = p.item;

//(2)队列中的节点和指定的节点相等则使用CAS将队列中的节点设置为null,由于CAS只能一个线程操作成功,失败的线程会继续遍历查找队列中是否还有其他匹配的节点。

            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;

}

containes(Object o) 

    判断队列中是否有与指定元素相等的元素,有则返回true,否则返回false。和size方法一样是一个不精度的匹配。

public boolean contains(Object o) {

    if (o == null) return false;

    for (Node p = first(); p != null; p = succ(p)) {

        E item = p.item;

        if (item != null && o.equals(item))

            return true;

    }

    return false;

}

    通过源码可以看出来ConcurrentLinkedQueue中都是使用的CAS没有加锁,而contains方法遍历队列的时候有可能其他的线程对ConcurrentLinkedQueue队列进行操作,从而导致统计元素的不准确性。

    今天的分享就到这,有看不明白的地方一定是我写的不够清楚,所有欢迎提任何问题以及改善方法。

你可能感兴趣的:(多线程并发编程11-ConcurrentLinkedQueue源码剖析)