ConcurrentLinkedQueue

ConcurrentLinkedQueue阅读笔记

一、简介

一种同步的链表队列,采用的自旋+CAS原子更新头尾节点控制出队入队操作

二、继承关系图

在这里插入图片描述

三、存储结构

采用链表的数据结构,数据更新和取出采用自旋+CAS原子操作

四、源码分析

内部类

  • 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) {
        // 原子操作初始化item,在指定对象的偏移量写入一个值
        UNSAFE.putObject(this, itemOffset, item);
    }
	
    // 更新item值
    boolean casItem(E cmp, E val) {
        //CAS原子更新当前节点的item值,cmp代表当前节点item值,val代表期望值
        return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
    }

    // 设置当前节点next的值
    void lazySetNext(Node<E> val) {
        //有序写入val到当前节点的next中,不保证可见性,但是保证有序性,也就是保证指令不会被重排 
        UNSAFE.putOrderedObject(this, nextOffset, val);
    }

    // CAS原子更新
    boolean casNext(Node<E> cmp, Node<E> val) {
        //CAS原子更新当前节点的next值,cmp是当前节点next值,val是期望值
        return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
    }

    // Unsafe mechanics

    /** 原子操作相关属性和static初始化 */
    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);
        }
    }
}

属性

/**	头结点 */
private transient volatile Node<E> head;
/** 尾节点 */
private transient volatile Node<E> tail;

/** Unsafe原子工具类,head、tail在对象中的偏移量 */
private static final sun.misc.Unsafe UNSAFE;
private static final long headOffset;
private static final long tailOffset;

构造

/** 构造方法一:无参构造 */
public ConcurrentLinkedQueue() {
    // 初始化head、tail节点 都为`new Node<E>(null)`且相同
    head = tail = new Node<E>(null);
}

/** 构造方法二:使用集合初始化节点 */
public ConcurrentLinkedQueue(Collection<? extends E> c) {
    // h:临时头节点
    // t:临时尾节点
    Node<E> h = null, t = null;
    // 循环遍历集合c
    // 把集合中元素生成Node对象
    for (E e : c) {
        checkNotNull(e);
        Node<E> newNode = new Node<E>(e);
        if (h == null)
            // 如果h==null,代表第一个节点,直接赋值给h和t
            h = t = newNode;
        else {
            // 如果h!=null,说明已经有头节点了,
            // 设置t节点next值
            t.lazySetNext(newNode);
            // 把t节点设置为新的临时尾节点
            t = newNode;
        }
    } 
    if (h == null)
        // 说明初始化集合参数没有元素,直接设置临时头节点h和临时尾巴节点t为`new Node<E>(null)`
        h = t = new Node<E>(null);
    // 把临时头节点 h 赋予给head
    // 把临时尾节点 t 赋予给tail
    head = h;
    tail = t;
}

主要方法

1、入队操作
  • add(E e):完全等同于offer(E e);
  • offer(E e):添加元素,自旋+CAS进行元素添加,如果添加成功true
/** 添加元素,完全等同于offer(E e) */
public boolean add(E e) {
    // 如果链表只要内存足够是不存在元素空间问题,所以直接调用offer即可
    return offer(e);
}

/** 
添加元素,自旋+CAS进行元素添加,如果添加成功true,
- 定位到链表尾节点,尝试把新节点放到后面
- 如果尾部变化了,则重新拉去尾节点,再重试,直到成功
*/
public boolean offer(E e) {
    // 效验元素,为null 抛出异常
    checkNotNull(e);
    // 生成待添加节点
    final Node<E> newNode = new Node<E>(e);

    // 循环:避免其他线程已经更新了尾节点
    // t:缓存 尾节点
    // p:第一次把 t 赋值给p,后续p会持续变化
    // q:p的next节点值,如果为null 则进入CAS更新
    for (Node<E> t = tail, p = t;;) {
        Node<E> q = p.next;
        if (q == null) {
            // 说明是尾节点的next,直接CAS更新p的next值
            // 如果成功了,就返回true
            // 如果不成功就重新取p.next重新尝试
            // p is last node
            if (p.casNext(null, newNode)) { 
                // Successful CAS is the linearization point
                // for e to become an element of this queue,
                // and for newNode to become "live".
                // 如果 p 不等于最后一次获取的尾节点 ,说明有其他线程先一步更新了尾节点
                // 所以,p有做更新,就开始CAS更新tail尾节点
                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的next 等于p,说明p节点已经被删除了
            // 重新设置 t = tail
            // 重新设置 p,
            // 如果原 t 和新获取tail的 t 不同,说明t有更新,直接用新的t作为p节点
            // 如果原 t 和新获取tail的 t 相同,说明没有节点(可能被出队了)
            p = (t != (t = tail)) ? t : head;
        else
            // Check for tail updates after two hops.
            // t 后面还有值
            // 重新设置 t = tail
            // 重新设置 p 的值
            p = (p != t && t != (t = tail)) ? t : q;
    }
} 
2、出队操作
  • 定位到头节点,尝试更新其值为null
  • 如果成功了,就成功出队
  • 如果失败了或者头结点变化了,就重新寻找头结点,并重试
  • 整个出队过程没有一点阻塞相关的代码,所以出队的时候不会阻塞线程,没找到元素就返回null
public E remove() {
    E x = poll();
    if (x != null)
        // 返回删除后的元素
        return x;
    else
        // 如果队列没有元素,则抛出异常
        throw new NoSuchElementException();
}

// 弹出头元素,成功返回弹出元素,没有就返回null
public E poll() {
    // 设置标记,用于控制剁成嵌套
    restartFromHead:
    for (;;) {
        // 尝试弹出头节点
        for (Node<E> h = head, p = h, q;;) {
            E item = p.item;

            if (item != null && p.casItem(item, null)) {
                // Successful CAS is the linearization point
                // for item to be removed from this queue.
                // 如果 item 不等于null
                // 并且 catItem更新为null 成功
                // 这个分支只有抢占到出队权限的全可以进来
                if (p != h) // hop two nodes at a time
                    // 说明之前有其他线程先取出过一次节点,p有变化了,
                    // 调用updateHead 把head更新为新q节点
                    // 如果(q = p.next) == null 说明没有节点了,
                    // 那么直接更新为p,也就是没变化,因为p等于h
                    updateHead(h, ((q = p.next) != null) ? q : p);
                // 返回出队的元素值
                return item;
            }
            // 下面三个分支说明头节点变了
            // 且 p 的 item 肯定为null
            else if ((q = p.next) == null) {
                // 如果p 的next 为空,说明队列中没有元素了
                // 更新h 为p,也就是空元素的节点
                updateHead(h, p);
                // 返回null
                return null;
            }
            else if (p == q)
                // 如果p 等于 p 的next,说明 p 已经出队了,重试
                // 标记之后是loop,才可以用continue 标记名 
                // 跳过这个循环
                continue restartFromHead;
            else
                // 将 p 设置为 p 的next (上面q = p.next)
                p = q;
        }
    }
}

/** 获得队列头节点,如果有值返回值,没有返回null */
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 不等于null,
                // 也可能没有节点
                updateHead(h, p);
                // 返回获得的节点,
                return item;
            }
            // 下面2个分支说明头节点变化了
            // 且p 的 item肯定为null
            else if (p == q)
                // 说明 p 已经出列了,重试
                continue restartFromHead;
            else
                // 将 p 设置为 p 的next (上面q = p.next)
                p = q;
        }
    }
}

/** 更新头结点 */
final void updateHead(Node<E> h, Node<E> p) {
    // 原子更新h为p成功后,延迟更新h的next为它自己
    // 这里用延迟更新是安全的,因为head节点已经变了
    // 只要入队出队的时候检查head有没有变化就行了,跟它的next关系不大
    if (h != p && casHead(h, p))
        h.lazySetNext(h);
}
3、移除指定节点
  • 找元素然后去删除,如果在过程自己(或其他线程已经)删除了元素,他会帮忙重新关联节点信息(移除被删除的节点)
public boolean remove(Object o) {
    // 如果 o 不为null 则走移除逻辑
    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;//缓存p.item
            if (item != null) {
                // 说明当前 p 是有值的,节点是存在的
                if (!o.equals(item)) {
                    // 说明次节点不是需要删除的对象,
                    // 那么就直接获取下一个值(此次就是p.next)
                    // succ(E e):获取下一个节点,如果为e 和e.next 相同则返回head
                    next = succ(p);
                    // 注意跳过后 会执行:pred = p, p = next
                    continue;
                }
                // 走到这里说明当前节点的item和需要删除的值 相同
                // 尝试更新 删除当前节点p的item值
                removed = p.casItem(item, null);
            }
            // 走到这里,只有2个可能,一个是item==null,或者p的item已经cas为null
            // 所以,item 一定是 null
            // 设置next 为 p的获取下一个值(此次就是p.next)
            // succ(E e):获取下一个节点,如果为e 和e.next 相同则返回head
            next = succ(p);
            if (pred != null && next != null) // unlink
                // 说明p元素已经被删除了,或者被其他线程删除了
                // 直接跳过p  把pred 的next 关联上next
                // 如:prev -> P -> next  变为 prev -> next
                pred.casNext(p, next);
            if (removed)
                //如果removed = true,说明已经删除成功。 
                return true;
        }
    }
    // 循环所有节点没有找到(或已经被其他线程移除了),移除失败
    return false;
}

补充

累死了,还补充啥

五、总结

不是阻塞队列,采用CAS+自旋更新头尾节点来控制出队和入队操作

发布了48 篇原创文章 · 获赞 17 · 访问量 5万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章