035_java.util.concurrent.ConcurrentLinkedDeque

继承关系

image.png
对比ConcurrentLinkedQueue在继承关系上,ConcurrentLinkedDeque多实现了一个Deque接口。Deque是双端队列的接口,在行为上扩充了Queue接口,可以在队列头与对列尾进行操作。

重要字段

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

private static final Node<Object> PREV_TERMINATOR, NEXT_TERMINATOR;

ConcurrentLinkedDeque中存在头尾结点,还有两个中止结点:

  • PREV_TERMINATOR:prev的终止节点,next指向自身,即 PREV_TERMINATOR.next = PREV_TERMINATOR。在 first 节点出列后,会把first.next指向自身(first.next=first),然后把prev设为 PREV_TERMINATOR
  • NEXT_TERMINATOR:next的终止节点,prev指向自身,即 NEXT_TERMINATOR.pre = NEXT_TERMINATOR。在 last 节点出列后,会把last.prev指向自身(last.prev=last),然后把next设为 NEXT_TERMINATOR

结点的类型是Node内部类,也是比较好理解的:

static final class Node<E> {
    volatile Node<E> prev;
    volatile E item;
    volatile Node<E> next;

    Node() {  // default constructor for NEXT_TERMINATOR, PREV_TERMINATOR
    }

    /**
     * Constructs a new node.  Uses relaxed write because item can
     * only be seen after publication via casNext or casPrev.
     */
    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);
    }

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

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

    // Unsafe mechanics

    private static final sun.misc.Unsafe UNSAFE;
    private static final long prevOffset;
    private static final long itemOffset;
    private static final long nextOffset;

    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> k = Node.class;
            prevOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("prev"));
            itemOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("item"));
            nextOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("next"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }
}

构造函数

// 空构造器
public ConcurrentLinkedDeque() {
    head = tail = new Node<E>(null);  // 头部和尾部都为空节点
}
// 通过集合,构造队列
public ConcurrentLinkedDeque(Collection<? extends E> c) {
    Node<E> h = null, t = null;
    for (E e : c) { 
        checkNotNull(e);
        Node<E> newNode = new Node<E>(e); //创建节点
        if (h == null)   // 第一次循环,创建第一个节点
            h = t = newNode;
        else {  // 在队尾插入元素
            t.lazySetNext(newNode);
            newNode.lazySetPrev(t);
            t = newNode;
        }
    }
    initHeadTail(h, t);  // 最后设置头部和尾部节点
}

总共两个构造器,第一个是无参构造器,内部初始化头尾结点,第二个构造器接受外部传递集合对象,其内部将数据组织为链表。

重要方法

入列

ConcurrentLinkedDeque的入队方法有很多,例如:addFirst(e)、addLast(e)、offerFirst(e)、offerLast(e)

public void addFirst(E e) { linkFirst(e); }  // 添加到首位
public void addLast(E e) { linkLast(e); }  //添加到末尾
public boolean offerFirst(E e) {  
    linkFirst(e);
    return true;
}
public boolean offerLast(E e) {
    linkLast(e);
    return true;
}

可以看到其内部调用link~方法,这里以linkFirst为例子:

private void linkFirst(E e) {
        checkNotNull(e); // 空检查
        final Node<E> newNode = new Node<E>(e);  //创建新节点
        restartFromHead:
        for (;;)
            // 前驱节点 != null && 前驱的前驱节点 != null
            for (Node<E> h = head, p = h, q;;) {          
                if ((q = p.prev) != null && (q = (p = q).prev) != null)
                    // 说明head被修改,返回head重新查找
                    p = (h != (h = head)) ? h : q;  
                // 自连接节点,不能从p开始查找,退出重新循环
                else if (p.next == p) 
                    continue restartFromHead;
                else {
                    // p 是第一个node,则更新新节点next指向p
                    // 尝试更新p的前驱指向新节点,更新失败则重新循环更新
                    newNode.lazySetNext(p); // CAS piggyback            
                    if (p.casPrev(null, newNode)) {
                        //新节点入队成功
                        if (p != h) // hop two nodes at a time
                            casHead(h, newNode);  //将新节点设置为头节点
                        return;
                    }
                    // 执行到此处说明CAS操作失败,有其它线程也在队首插入元素
                }         
    }
}

出列

ConcurrentLinkedDeque的出队一样分为队首、队尾两种情况:removeFirst()、pollFirst()、removeLast()、pollLast()。

public E removeFirst() { return screenNullResult(pollFirst()); }
public E removeLast() { return screenNullResult(pollLast()); }// 移除第一个节点
public E pollFirst() {   // first() 找到第一个节点, succ()返回下一个节点
    for (Node<E> p = first(); p != null; p = succ(p)) {
        E item = p.item;  // 节点数据
        if (item != null && p.casItem(item, null)) {  // CAS 将当前节点数据清空
            unlink(p);   //取消当前节点连接
            return item;  //返回节点数据
        }
    }
    return null;
}// 移除最后一个节点
public E pollLast() {   // last() 找到最后一个节点, pred() 返回上一节点
    for (Node<E> p = last(); p != null; p = pred(p)) {
        E item = p.item;
        if (item != null && p.casItem(item, null)) {
            unlink(p);
            return item;
        }
    }
    return null;
}

上面涉及到的方法first()、last()、succ()、pred()

// 返回首节点
Node<E> first() {
    restartFromHead:
    for (;;)         // 从head开始往前找
        for (Node<E> h = head, p = h, q;;) {
            if ((q = p.prev) != null && (q = (p = q).prev) != null)            // 如果head被修改则返回新的head重新查找,否则继续往前(pred)查找
                p = (h != (h = head)) ? h : q;
            else if (p == h || casHead(h, p)) // 找到的节点不是head节点,CAS修改head
                return p;
            else
                continue restartFromHead;
        }
}

// 返回尾结点
Node<E> last() {
    restartFromTail:
    for (;;)
        for (Node<E> t = tail, p = t, q;;) {
            if ((q = p.next) != null && (q = (p = q).next) != null)
                p = (t != (t = tail)) ? t : q;
            else if (p == t || casTail(t, p))
                return p;
        else
            continue restartFromTail;
    }
}

// 返回后继节点
final Node<E> succ(Node<E> p) {
    Node<E> q = p.next;
    return (p == q) ? first() : q;
}

// 返回前驱节点
final Node<E> pred(Node<E> p) {
    Node<E> q = p.prev;
    return (p == q) ? last() : q;
}

CAS 修改节点的 item 为 null(即 “逻辑删除-logical deletion”),然后调用unlink§方法解除节点链接,最后返回 item。unlink§是移除节点的主方法

void unlink(Node<E> x) {
        final Node<E> prev = x.prev; 
        final Node<E> next = x.next;
        if (prev == null) {  // 前驱为空,表示是第一个节点
            unlinkFirst(x, next);
        } else if (next == null) { // 后继为空,表示是最后一个节点
            unlinkLast(x, prev);
        } else {  // 中间节点
            Node<E> activePred, activeSucc;
            boolean isFirst, isLast;
            int hops = 1;
        // 从被删除节点往前找到第一个有效前驱节点
            for (Node<E> p = prev; ; ++hops) {
                if (p.item != null) { // 找到有效节点
                    activePred = p;
                    isFirst = false;
                    break;
                }
                Node<E> q = p.prev;
                if (q == null) {  // 已经到了头部了
                    if (p.next == p)  // 发现自链接,直接返回
                        return;
                    activePred = p;
                    isFirst = true;
                    break;
                }
                else if (p == q)  //同样是自链接
                    return;
                else  // 更新循环指针
                    p = q;
            }

            // 从被删除节点往后找到第一个有效后继节点
            for (Node<E> p = next; ; ++hops) {
                if (p.item != null) {  // 找到有效节点
                    activeSucc = p;
                    isLast = false;
                    break;
                }
                Node<E> q = p.next;
                if (q == null) {  // 已经到队尾了
                    if (p.prev == p)  // 发现自链接,直接返回
                        return;
                    activeSucc = p;
                    isLast = true;
                    break;
                }
                else if (p == q) //自链接
                    return;
                else
                    p = q; // 更新循环指针
            }        // 如果已经积累了超过临界值的逻辑删除节点,或者是内部节点删除,我们需要进一步处理unlink / gc-unlink
            if (hops < HOPS && (isFirst | isLast))
                return;
        // 移除有效前驱和后继节点之间的那些节点(都是逻辑删除的节点),包括x节点本身,就是使 有效前驱 和 后继节点相连
            skipDeletedSuccessors(activePred);
            skipDeletedPredecessors(activeSucc);        // 如果更新的开头或者结尾,那么就可以尝试进行gc-unlink
            if ((isFirst | isLast) &&                // 确保前驱和后继的状态没有被改变              (activePred.next == activeSucc) && (activeSucc.prev == activePred) &&               (isFirst ? activePred.prev == null : activePred.item != null) && (isLast  ? activeSucc.next == null : activeSucc.item != null)) {          // 确保x节点不能从head/tail节点被访问
                updateHead(); 
                updateTail(); 
                x.lazySetPrev(isFirst ? prevTerminator() : x); // 前驱终结节点
                x.lazySetNext(isLast  ? nextTerminator() : x); // 后继终结节点
            }
        }
    }


// 从first开始往后找到第一个有效节点,直到找到或者到达队列的最后一个节点为止,并把first的直接后继指向该有效节点:
// 1) 如果first的后继本身就是有效节点,不做任何处理
// 2) 否则往后依次找到第一个有效节点,并把first的后继指向该有效节点
private void unlinkFirst(Node<E> first, Node<E> next) {
    for (Node<E> o = null, p = next, q;;) {     // p是有效节点 || p是最后一个节点
        if (p.item != null || (q = p.next) == null) {       // 第一次循环,o为null。从第二次循环开始,p是o的后继有效节点,并且p还没有断开o,将first后继指向该有效节点p
            if (o != null && p.prev != p && first.casNext(next, p)) {
                skipDeletedPredecessors(p);          // 确保first还是第一个节点,没有被其他线程改变状态,并且它和它的后继节点p是直接相连接的,这种关系没有被破坏
                if (first.prev == null && (p.next == null || p.item != null) && p.prev == first) {

                    updateHead(); // Ensure o is not reachable from head
                    updateTail(); // Ensure o is not reachable from tail
                    
                    o.lazySetNext(o);
                    o.lazySetPrev(prevTerminator());
                }
            }
            return;
        }
        else if (p == q)
            return;
        else {
            o = p;
            p = q;
        }
    }
}

这里以pollFirst出队方法为例,其他方法逻辑都一样。其逻辑如下:
(1)先通过first()拿到队列头部的第一个节点,如果是活动节点(item不为null),则直接将item置为null,即完成了删除节点的第一步逻辑删除,
(2)执行unlink方法执行删除节点
(3)GC-unlinking,unlink方法针对节点在不同的位置按不同的逻辑处理
1)如果出队的节点是队列的第一个节点,则执行unlinkFirst;
2)如果是队列的最后一个节点,则执行unlinkLast,③否则表示是内部节点,执行unlink本身的通用节点逻辑。
其中unlinkFirst的逻辑其实就分两个部分:
第一部分是实现从被移除节点p开始往后(队尾)找到第一个有效节点,直到找到或者到达队列的最后一个节点为止,并把p的直接后继指向该有效节点(如果本身不是其后继节点的话),其中的skipDeletedPredecessors方法实现将刚刚找到的后继节点的前驱也指向节点p,即完成它们的互联,这一步就是所谓的unlinking,使队列的活动节点无法访问被删除的节点;
第二部分是通过updateHead、updateTail使被删除的节点无法从head/tail可达,最后让被删除节点后继自连接,前驱指向前向终结节点。如果是内部节点出队,执行unlink本身,先找到被删除节点x的有效前驱和后继节点,并记录它们中间的已经被逻辑删除的节点个数,如果已经积累了超过阈值的节点个数,或者是内部节点删除,我们需要进一步处理unlink/gc-unlink
1)首先使被删除节点的有效前驱节点和后继节点互联,就相当于导致活动节点不会访问到中间已经被逻辑删除的节点(unlinking);
2)若1)中导致重新链接到了对头或队尾,则通过updateHead、updateTail使被删除的节点无法从head/tail可达,最后让被删除节点自连接或者执行终结节点(GC-unlinking)。

总结

ConcurrentLinkedDeque使用了自旋+CAS的非阻塞算法来保证线程并发访问时的数据一致性。由于队列本身是一种双链表结构,所以虽然算法看起来很简单,但其实需要考虑各种并发的情况,实现复杂度较高,并且ConcurrentLinkedDeque不具备实时的数据一致性,实际运用中,如果需要一种线程安全的栈结构,可以使用ConcurrentLinkedDeque。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值