SynchronousQueue之TransferQueue(公平队列)源码分析

347 阅读7分钟

简介

SynchronousQueue是一个同步阻塞队列,它的每个插入操作或者获取操作都要等待其它线程相应的获取或者插入,内部提供两种策略,公平模式和非公平模式,上一节分析了默认策略,非公平模式,这节就分析下公平队列-TransferQueue。

TransferQueue

主要属性

/** Head of queue */
transient volatile QNode head; // head节点
/** Tail of queue */
transient volatile QNode tail; // 尾部节点
/**
 * 这个属性有点特别,它的存在就是记录即将要删除的尾结点的prev结点
 * 哈哈
 * 说的有点绕
 * 假如当前删除的结点是尾结点,是不能被删除的 cleanMe指向的就是当前尾结点的前置节点
 * 因为新入队的节点都是挂到尾结点之后的,如果删除了尾结点,会导致后面的节点丢失
 *
 * */
transient volatile QNode cleanMe;

主要方法

void advanceHead(QNode h, QNode nh) {
    if (h == head &&
        UNSAFE.compareAndSwapObject(this, headOffset, h, nh)) // 原子更新nh为head节点
        h.next = h; // 之前的head节点next指向自己
}

 void advanceTail(QNode t, QNode nt) {
    if (tail == t)
        UNSAFE.compareAndSwapObject(this, tailOffset, t, nt); // 原子更新nt为tail节点
}

boolean casCleanMe(QNode cmp, QNode val) {
    return cleanMe == cmp &&
        UNSAFE.compareAndSwapObject(this, cleanMeOffset, cmp, val); // // 原子更新val为cleanMe节点
}

主要内部类

static final class QNode {
    volatile QNode next;          // 下一个节点
    volatile Object item;         // 元素
    volatile Thread waiter;       // 对应线程
    final boolean isData;         // 是否是数据

    QNode(Object item, boolean isData) {
        this.item = item;
        this.isData = isData;
    }

    boolean casNext(QNode cmp, QNode val) {
        return next == cmp && // 如果next是指向cmp,则原子更新当前节点的next为val
            UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
    }

    boolean casItem(Object cmp, Object val) {
        return item == cmp && // 如果当前节点的item指向cmp,则原子更新当前节点的item为val
            UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
    }

    /**
     * Tries to cancel by CAS'ing ref to this as item.
     */
    void tryCancel(Object cmp) { // 节点取消,让当前节点的item指向自己
        UNSAFE.compareAndSwapObject(this, itemOffset, cmp, this);
    }

    boolean isCancelled() { // 判断节点有没有取消,如果item指向自己,则节点已经取消
        return item == this;
    }

    /**
     * Returns true if this node is known to be off the queue
     * because its next pointer has been forgotten due to
     * an advanceHead operation.
     */
    boolean isOffList() { // 判断节点有没有出队,如果next指向自己,则出队
        return next == this;
    }

    // Unsafe mechanics
    private static final sun.misc.Unsafe UNSAFE; // 魔法类
    private static final long itemOffset; // item相对QNode偏移量
    private static final long nextOffset; // next相对QNode偏移量

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

transfer

E transfer(E e, boolean timed, long nanos) {
    /* Basic algorithm is to loop trying to take either of
     * two actions:
     *
     * 1. If queue apparently empty or holding same-mode nodes,
     *    try to add node to queue of waiters, wait to be
     *    fulfilled (or cancelled) and return matching item.
     *
     * 2. If queue apparently contains waiting items, and this
     *    call is of complementary mode, try to fulfill by CAS'ing
     *    item field of waiting node and dequeuing it, and then
     *    returning matching item.
     *
     * In each case, along the way, check for and try to help
     * advance head and tail on behalf of other stalled/slow
     * threads.
     *
     * The loop starts off with a null check guarding against
     * seeing uninitialized head or tail values. This never
     * happens in current SynchronousQueue, but could if
     * callers held non-volatile/final ref to the
     * transferer. The check is here anyway because it places
     * null checks at top of loop, which is usually faster
     * than having them implicitly interspersed.
     */

    QNode s = null; // constructed/reused as needed
    boolean isData = (e != null); // e 不为null是生产节点,反之消费节点

    for (;;) {
        QNode t = tail; // 尾结点
        QNode h = head; // 头结点
        if (t == null || h == null)         // 没有初始化,重新循环
            continue;                       // spin

        if (h == t || t.isData == isData) { // 如果head节点等于tail节点或者当前模式和尾部模式一致
            QNode tn = t.next; // 当前尾结点的下一个节点,正常情况是null,如果不是null,说明有其它其它节点已经先于当前节点插入了
            if (t != tail)                  // 尾结点已经变化,重新循环
                continue;
            if (tn != null) {               // 如果不是null,说明有其它节点已经先于当前节点插入了
                advanceTail(t, tn); // 帮助更新tn为新的尾结点
                continue; // 尾结点已经变化,重新循环
            }
            if (timed && nanos <= 0)        // 如果设置了超时且已经超时,直接返回null
                return null;
            if (s == null)
                s = new QNode(e, isData); // 创建一个新的节点
            if (!t.casNext(null, s))        // 更新当前尾结点t的next为刚创建的s节点,如果更新失败,则重新循环
                continue;

            advanceTail(t, s);              // 更新尾节点为s
            Object x = awaitFulfill(s, e, timed, nanos); // 等待匹配
            if (x == s) {                   // itme元素等于自己,说明该节点被中断或者超时
                clean(t, s);                // 清除此节点
                return null;
            }



            if (!s.isOffList()) {           // 如果还未出队
                /**
                 * 推进s节点为head节点,也就是让head指向s
                 * 有没有发现这里的第一个参数传入的是t节点?
                 * 下面列举一个场景来推导一下为什么要传入t,列举的场景只是为了说明问题,
                 * 真实的执行可能不是这样,因为并发场景什么情况都有可能产生,但不影响说明问题。
                 * 假如当前时刻有三个节点 A B C
                 * A 是dummy node 也就是head指向它
                 * B 是 C的前驱节点
                 * C 是尾结点 也就是s节点
                 * 这三个节点都被封锁到当前的方法栈中,从当前方法栈来看s就是尾结点,b就是中间节点
                 * 但程序执行到这里,从其它线程(工作空间/方法栈)来看 C 已经是head.next,B 是head
                 *
                 * ~~~~~
                 * 说的可能还是有点绕,但我已经尽力了,哈哈哈哈,如果还不明白,好好研究下,肯定会想明白的。
                 * 并发编程不能按照正常套路出牌
                 */
                advanceHead(t, s);
                if (x != null)              // take等待put x不为null
                    s.item = s;
                s.waiter = null;
            }
            return (x != null) ? (E)x : e; // 返回元素值,如果返回x是take等待put匹配,返回e是put等待take匹配

        } else {                            // 如果队列中已经有元素且尾结点模式和当前线程模式不同,走这里
            QNode m = h.next;               // 永远匹配的是head.next 因为head节点是dummy node
            if (t != tail || m == null || h != head) // 当前方法栈中的tail或者head已经不是tail和head了或者head.next是null,重新循环
                continue;                   // inconsistent read

            Object x = m.item;  // 拿到m节点的元素
            if (isData == (x != null) ||    // 节点匹配只能是take等待put或者put等待take,此条件如果成立,说明两者模式一致
                x == m ||                   // m 节点已经取消
                !m.casItem(x, e)) {         // 原子更新item 如果put等待take,会把item更新为null,反之更新为put的元素值
                advanceHead(h, m);          // 有一个条件成立,就说明m不能再匹配了,那就把它更新为haed节点吧
                continue;
            }

            advanceHead(h, m);              // 推进m为head节点
            LockSupport.unpark(m.waiter);   // 唤醒m对应的线程
            return (x != null) ? (E)x : e;  // 返回元素值,如果返回x是take等待put匹配,返回e是put等待take匹配
        }
    }
}
  1. 如果队列为空(h == t)或者当前模式和尾部模式是否相同,如果相同则执行插入节点的操作
  2. 模式不同,尝试去匹配,匹配从head.next开始,若head.next不是null,不管是否匹配成功,最后都会推进head.next为新的head

awaitFulfill

Object awaitFulfill(QNode s, E e, boolean timed, long nanos) {
    /* Same idea as TransferStack.awaitFulfill */
    final long deadline = timed ? System.nanoTime() + nanos : 0L; // 如果设置了超时,这里就计算下多久之后超时,反之是0
    Thread w = Thread.currentThread(); // 当前线程
    /**
     * 如果当前s节点排行老二,可能很快就会被匹配,先自旋等待一会,这里的等待是值得的
     * 自旋等待的次数我就不描述了,看看就明白了
     * 如果不是 就会被挂起
     */
    int spins = ((head.next == s) ?
                 (timed ? maxTimedSpins : maxUntimedSpins) : 0);
    for (;;) {
        if (w.isInterrupted()) // 如果当前线程被中断,尝试取消
            s.tryCancel(e); // 取消就是尝试当前node的item等于当前node
        Object x = s.item;
        if (x != e) // 匹配成功或者超时或者被中断了,就跳出循环了
            return x;
        if (timed) {
            nanos = deadline - System.nanoTime();
            if (nanos <= 0L) {
                s.tryCancel(e); // 超时,尝试取消,下次循环就退出了
                continue;
            }
        }
        if (spins > 0) // 每次循环自旋次数就-1
            --spins;
        else if (s.waiter == null)
            s.waiter = w;
        else if (!timed) // 没有设置超时等待,直接挂起
            LockSupport.park(this);
        else if (nanos > spinForTimeoutThreshold) // 设置了超时了等待且剩余的超时时间大于1000纳秒,就超时挂起剩余的时间
            LockSupport.parkNanos(this, nanos);
    }
}

clean

void clean(QNode pred, QNode s) {
    s.waiter = null; // forget thread
    /*
     * At any given time, exactly one node on list cannot be
     * deleted -- the last inserted node. To accommodate this,
     * if we cannot delete s, we save its predecessor as
     * "cleanMe", deleting the previously saved version
     * first. At least one of node s or the node previously
     * saved can always be deleted, so this always terminates.
     */
    while (pred.next == s) { // 如果pred的next还是s节点
        QNode h = head;  // head节点,把当前head节点封锁到方法栈中
        QNode hn = h.next;   // head.next节点
        /**
         * 如果此时pred是head节点,那么就会推进hn(s节点)为head节点,如果执行成功
         * 程序就退出了,此时pred.next已经指向自己了,pred.next == s条件不成立
         */
        if (hn != null && hn.isCancelled()) { // 如果hn已经取消
            advanceHead(h, hn); // 推进head指向hn节点
            continue;
        }
        QNode t = tail;      // 把当前tail节点封锁到方法栈中
        /**
         * 这里做个假设,推导t == h成立:
         * 假如 队列中有两个节点 A(pred) B(s), A也就是dummy node,B在队列中等待匹配
         * 时刻1:线程t1判断尾部节点模式和自己的模式不同,拿到当前的head(pred)节点封锁到自己的方法栈中,
         *       然后执行transfer方法的else分支,如果此时有外部线程中断了B节点所拥有的线程,且cpu时间片切换
         * 时刻2:B节点被中断唤醒,判断 while (pred.next == s) 成立,此时cpu时间片切换到3
         * 时刻3:t1线程判断到head.next(s) 已经取消,执行advanceHead(h, m);   把m推向head节点,cpu时间片切换
         * 时刻4:从时刻2执行到地方开始执行,进入while循环内部,拿到当前的head和tail,其实此时的head和tail已经同时
         *       指向了同一个节点了,所以判断 t == h 此时是成立的
         * 多线程并发 比较复杂,各种情况都有可能发生。以上是通过我自己的理解推导出来的,如果不正确,请指正
         * 如果以后想到更好的描述方式,在更新~~~
         * 大概意思我感觉还是正确的 哈哈哈
         */
        if (t == h) // 队列已空,直接退出
            return;
        QNode tn = t.next;
        if (t != tail) // 尾结点已经改变,重新循环
            continue;
        if (tn != null) { // 有新节点加入,推进tn为tail节点
            advanceTail(t, tn);
            continue;
        }
        if (s != t) {        // 如果取消的不是尾结点,直接删除,这种删除是安全的
            QNode sn = s.next;
            if (sn == s || pred.casNext(s, sn))
                return;
        }
        /**
         * 如果程序执行到这里,cleanMe就要等登场了,说明此时要删除的节点是尾结点
         * cleanMe记录的是尾结点的前驱节点
         * 这里不是直接删除,只是把当前尾结点的前驱记录到cleanMe中,等到下次(有新的尾结点取消)其它节点尝试帮助删除
         */
        QNode dp = cleanMe;
        if (dp != null) {
            QNode d = dp.next; // 真正要删除的节点
            QNode dn;
            if (d == null ||               // 要删除的节点(s)是null
                d == dp ||                 // cleanMe(pred)指向的节点已经出队
                !d.isCancelled() ||        // d 也就是s没有取消
                (d != t &&                 //
                 (dn = d.next) != null &&  //   has successor
                 dn != d &&                //   that is on list
                 dp.casNext(d, dn)))       // 让pred指向s节点的next,s节点出队
                casCleanMe(dp, null); // 清除cleanMe
            if (dp == pred)
                return;      // s is already saved node
        } else if (casCleanMe(null, pred)) // cleanMe指向pred,等待被删除
            return;          // Postpone cleaning s
    }
}

总结

SynchronousQueue相对于其它的阻塞队列还是比较复杂的,代码看了很久,有些地方可能还是没有特别明白,以上都是基于自己的理解总结的。若有不当,烦请指正