【JAVA多线程】ConcurrentLinkedQueue源码分析

在这里插入图片描述

ConcurrentLinkedQueue

基于链接节点的无界线程安全队列。此队列对元素进行 FIFO(先进先出)排序。队列的头部是在队列中时间最长的元素。队列的尾部是在队列中时间最短的元素。新元素被插入到队列的尾部,队列检索操作获取队列头部的元素。ConcurrentLinkedQueue当许多线程将共享对公共集合的访问时,A是一个合适的选择。与大多数其他并发集合实现一样,此类不允许使用null元素。

此实现采用了一种有效的非阻塞 算法

用例

import java.util.concurrent.*;

public class Test {


    public static void main(String[] args) {

        ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue();
        queue.offer("1");
        System.out.println("offer后,队列是否空?" + queue.isEmpty());
        System.out.println("从队列中poll:" + queue.poll());
        System.out.println("pool后,队列是否空?" + queue.isEmpty());


    }
}

属性

// 链表头节点
private transient volatile Node<E> head;
// 链表尾节点
private transient volatile Node<E> tail;

内部类Node

 private static class Node<E> {
        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);
        }

        boolean casItem(E cmp, E val) {
            return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
        }

        void lazySetNext(Node<E> val) {
            UNSAFE.putOrderedObject(this, nextOffset, val);
        }

        boolean casNext(Node<E> cmp, Node<E> val) {
            return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
        }

        // Unsafe mechanics

        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);
            }
        }
    }

入队

在此队列的尾部插入指定元素。

public boolean offer(E e) {
        checkNotNull(e);//是null则抛出异常
        final Node<E> newNode = new Node<E>(e);

        for (Node<E> t = tail, p = t;;) {初始p指针和t指针都指向尾节点
            Node<E> q = p.next;
            if (q == null) {//q为null,则代表p为尾节点,则将p.next指向新节点
                // p is last node
                if (p.casNext(null, newNode)) {
                 
                    if (p != t) // hop two nodes at a time
                        casTail(t, newNode);  // Failure is OK.
                    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;
        }
    }

获取到当前尾节点p=tail:

如果p.next=null,代表是真正的尾节点,将新节点链入p.next=newNode。此时检查tail是否还是p,如果不是p了,此时更新tail为最新的newNode(只有在tail节点后面tail.next成功添加的元素才不需要更新tail,其实更新不更新tail是交替的,即每添加俩次更新一次tail)。
如果p.next=p,此时其实是p.nextpnull,此时代表p被删除了,此时需要从新的tail节点检查,如果此时tail节点还是原来的tail(原来的tail在p前面,肯定也被删除了),那就只能从head节点开始遍历了
如果p.next!=null,代表有别的线程抢先添加元素了,此时需要继续p=p.next遍历获取是null的节点(此时需要如果tail变了就使用新的tail往后遍历)

出队

poll出队:
获取到当前头节点p=head:如果成功设置了item为null,即p.catItem(item,null),
如果此时被其他线程抢走消费了,此时需要p=p.next,向后继续争抢消费,直到成功执行p.catItem(item,null),此时检查p是不是head节点,如果不是更新p.next为头结点

总结

offer:

找到尾节点,将新节点链入到尾节点后面,tail.next=newNode,

由于多线程操作,所以拿到p=tail后cas操作执行p.next=newNode可能由于被其他线程抢去而执行不成功,此时需要p=p.next向后遍历,直到找到p.next=null的目标节点。继续尝试向其后面添加元素,添加成功后检查p是否是tail,如果不是tail,则更新tail=p,添加不成功继续向后next遍历

poll:

获取到当前头节点p=head:如果成功设置了item为null,即p.catItem(item,null),

如果此时被其他线程抢走消费了,此时需要p=p.next,向后继续争抢消费,直到成功执行p.catItem(item,null),此时检查p是不是head节点,如果不是更新头结点head=p.next(因为p已经删除了)

更新tail和head:

不是每次添加都更新tail,而是间隔一次更新一次(head也是一样道理):第一个抢到的线程拿到tail执行成功tail.next=newNode1此时不更新tail,那么第二个线程再执行成功添加p.next=newNode2会判断出p是newNode1而不是tail,所以就更新tail为newNode2。

tail节点不总是最后一个,head节点不总是第一个设计初衷:

让tail节点永远作为队列的尾节点,这样实现代码量非常少,而且逻辑非常清楚和易懂。但是这么做有个缺点就是每次都需要使用循环CAS更新tail节点。如果能减少CAS更新tail节点的次数,就能提高入队的效率。

猜你喜欢

转载自blog.csdn.net/qq_15604349/article/details/124636737