SynchronousQueue也是阻塞队列中的一种,它用在线程池中可以让线程池动态调整线程数量,任务多它就创建多点线程去执行任务,任务少它就会把多余的线程释放掉。这在一定程度上提高了并发的吞吐量,但是需要注意的是系统资源是否承受的起线程的扩容。

我们从代码层面去理解,可以把插入操作和取出操作分别封装为节点A、节点B。那么可以把SynchronousQueue说成是配对管道,管道中只会保留一种节点,要么全是A,要么全是B。当全是节点A的管道遇到节点B时,就完成了一个配对,管道会释放掉一个节点A。

也可以把SynchronousQueue当作是一种手把手传递东西,一个物体得经过两个手才算传递完成,就是我手上有一个物体,得有下一个人接收才算完。也可以比作我大A股,假如我手上有不错的票,得有接盘的韭菜买,我才会赚到钱。

一、代码结构

从如下类关系中可以看到,SynchronousQueue继承了AbstractQueue,说明它也是集合框架中的成员,BlockingQueue的子类基本上都是集合框架成员。

从构造器中看到,它有一个boolean的入参,用来指定是否公平模式。true的话会使用TransferQueue队列作为管道,它可以保证先进先出。false使用TransferStack,提供后进先出的实现方式。

public class SynchronousQueue<E> extends AbstractQueue<E>
    implements BlockingQueue<E>, java.io.Serializable {
	public SynchronousQueue() {
        this(false);
    }
    public SynchronousQueue(boolean fair) {
        transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
    }
	
	/** 传递模式,0表示来接收的,也就是获取操作*/
	static final int REQUEST    = 0;
    /** 1代表本身有东西,需要传递给他人,对应的是插入操作 */
    static final int DATA       = 1;
    /** 2代表已经配对成功了 */
    static final int FULFILLING = 2;
}

二、出入队列操作

SynchronousQueue有个很特殊的地方就是,它的出入队列其实最后的逻辑都是在同一个方法中,下面我们来看看它的两种实现方式。

1. 后进先出实现方式

这也叫做非公平方式,先来看看它的入队列方法。

// put方法插入队列,其实也可以说是传递物体,
	public void put(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();
        // 调用transfer方法
        if (transferer.transfer(e, false, 0) == null) {
            Thread.interrupted();
            throw new InterruptedException();
        }
    }
	// 无阻塞入队列
	public boolean offer(E e) {
        if (e == null) throw new NullPointerException();
        // 也是直接调用了transfer()方法
        return transferer.transfer(e, true, 0) != null;
    }
    
    // 有阻塞出队列
    public E take() throws InterruptedException {
        E e = transferer.transfer(null, false, 0);
        if (e != null)
            return e;
        Thread.interrupted();
        throw new InterruptedException();
    }
	
	// 无阻塞出队列,通过参数true和0控制
	public E poll() {
        return transferer.transfer(null, true, 0);
    }
    
	E transfer(E e, boolean timed, long nanos) {
            SNode s = null; // constructed/reused as needed
            // e为空说明是获取操作,对应REQUEST。否则是DATA
            int mode = (e == null) ? REQUEST : DATA;
            for (;;) {
                SNode h = head;
                // 头节点为空,或者头节点模式跟当前操作一致,说明没有配对成功。
                // 这里只需要判断头节点即可,因为管道中只会保留一种类型的节点
                if (h == null || h.mode == mode) {  // empty or same-mode
                    // 判断是否需要等待,put是不需要等待的,take方法需要等待
                    if (timed && nanos <= 0) {      // can't wait
                        if (h != null && h.isCancelled())
                        	// 这里是替换掉失效的头节点
                            casHead(h, h.next);     // pop cancelled node
                        else
                            return null;
                    } 
					// 这一行可以说明它就是是后进先出了,直接把头节点替换为当前节点
					else if (casHead(h, s = snode(s, e, h, mode))) {
                    	// 等待配对,这个方法这里可能会阻塞,如果返回的节点m等于当前节点,
                    	// 那么说明配对失败,直接返回null,下面分析这个方法
                        SNode m = awaitFulfill(s, timed, nanos);
                        // 阻塞被唤醒,如果m == s 说明配对不成功
                        if (m == s) {               // wait was cancelled
                            // 断开当前节点
                            clean(s);
                            return null;
                        }
                        if ((h = head) != null && h.next == s)
                            // 这一步是帮忙释放节点的,跟下方的”代码标记A”有同样的功效
                            casHead(h, s.next);     // help s's fulfiller
                        return (E) ((mode == REQUEST) ? m.item : s.item);
                    }
                } 
				// 这个分支是准备配对的
				else if (!isFulfilling(h.mode)) { // try to fulfill
                    // 节点取消配对,也可以说是出入队列失败了
                    if (h.isCancelled())            // already cancelled
                        casHead(h, h.next);         // pop and retry
                    else if (casHead(h, s=snode(s, e, h, FULFILLING|mode))) {
                        for (;;) { // loop until matched or waiters disappear
                            SNode m = s.next;       // m is s's match
                            if (m == null) {        // all waiters are gone
                            	// 能够配对的节点为空了,所以设置头节点也为空
                                casHead(s, null);   // pop fulfill node
                                s = null;           // use new node next time
                                break;              // restart main loop
                            }
                            SNode mn = m.next;
                            // m和s做配对,tryMatch方法后面有介绍
                            if (m.tryMatch(s)) {
                                // 代码标记A
                                casHead(s, mn);     // pop both s and m
                                // 这里的返回很奇妙了,他就是出入队列可以复用同一个方法的原理所在
                                return (E) ((mode == REQUEST) ? m.item : s.item);
                            } else                  // lost match
                            	// 如果配对失败,说明有其他节点配对了m,我们从mn开始接着配对。因为这里没加锁,全靠CAS去完成配对,所以失败是有可能的。
                                s.casNext(m, mn);   // help unlink
                        }
                    }
                } else {                            // help a fulfiller
                	/* 进到这个分支,说明有线程正在第二个分支,头节点正在配对中,那么我们就帮助第二个分支,加速配对。
                	这个分支的作用其实可以忽略的,我这里也搞不懂为啥需要这个分支。
                	有可能是担心第二个分支的for语句被操作系统限制运行了,由于线程调度的原因,正在执行第二分支的线程可能被挂起了。
                	当另外的线程进入到第三个分支就可以加速后面的配对了。*/
                    SNode m = h.next;               // m is h's match
                    if (m == null)                  // waiter is gone
                        casHead(h, null);           // pop fulfilling node
                    else {
                        SNode mn = m.next;
                        if (m.tryMatch(h))          // help match
                            casHead(h, mn);         // pop both h and m
                        else                        // lost match
                            h.casNext(m, mn);       // help unlink
                    }
                }
            }
        }

		SNode awaitFulfill(SNode s, boolean timed, long nanos) {   
            final long deadline = timed ? System.nanoTime() + nanos : 0L;
            Thread w = Thread.currentThread();
            int spins = (shouldSpin(s) ? (timed ? maxTimedSpins : maxUntimedSpins) : 0);
            for (;;) {
            	// 如果被中断了,那么取消配对,s.match为自己
                if (w.isInterrupted())
                    s.tryCancel();
                // 返回配对结果,如果配成功的话,match非空不等于自己
                SNode m = s.match;
                if (m != null)
                    return m;
                if (timed) {
                    nanos = deadline - System.nanoTime();
                    if (nanos <= 0L) {
                        s.tryCancel();
                        continue;
                    }
                }
                // 用spins控制轮询多次看是否配对成功
                if (spins > 0)
                    spins = shouldSpin(s) ? (spins-1) : 0;
                else if (s.waiter == null)
                    s.waiter = w; // establish waiter so can park next iter
                // 不需要阻塞倒计时
                else if (!timed)
                    LockSupport.park(this);
                // spinForTimeoutThreshold=1000,即大于这个数才需要调用可以倒计时的阻塞方法
                else if (nanos > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanos);
            }
        }
       
     // 当出入队列失败时会断开当前节点
     void clean(SNode s) {
     		// 置空当前节点item
            s.item = null;   // forget item
            s.waiter = null; // forget thread
            SNode past = s.next;
            if (past != null && past.isCancelled())
                past = past.next;

            // 从head开始遍历,释放past之前的失效的头节点,用下一个节点替换为新的节点
            SNode p;
            while ((p = head) != null && p != past && p.isCancelled())
                casHead(p, p.next);

            // 从新的head开始直到当前节点的下一个节点,释放掉失效的节点
            while (p != null && p != past) {
                SNode n = p.next;
                if (n != null && n.isCancelled())
                    p.casNext(n, n.next);
                else
                    p = n;
            }
        } 
	/** 生成一个新的节点 */
	static SNode snode(SNode s, Object e, SNode next, int mode) {
            if (s == null) s = new SNode(e);
            s.mode = mode;
            s.next = next;
            return s;
     }

	// 判断是否配对成功
	static boolean isFulfilling(int m) { return (m & FULFILLING) != 0; }

	// 这个方法挺重要的,有可能是被transfer方法的第二,第三个分支同时调用,谁先调用就唤醒阻塞方法,然后返回true。后调用的方法也没关系,还有个match==s作为是否匹配成功的判断
	boolean tryMatch(SNode s) {
                if (match == null &&
                    UNSAFE.compareAndSwapObject(this, matchOffset, null, s)) {
                    Thread w = waiter;
                    if (w != null) {    // waiters need at most one unpark
                        waiter = null;
                        LockSupport.unpark(w);
                    }
                    return true;
                }
                return match == s;
            }

2. 先进先出实现方式

后进先出是往队列尾部插入节点,队列头部取出节点。

E transfer(E e, boolean timed, long nanos) {
            QNode s = null; // constructed/reused as needed
            boolean isData = (e != null);

            for (;;) {
                QNode t = tail;
                QNode h = head;
                if (t == null || h == null)         // saw uninitialized value
                    continue;                       // spin
				// 队列为空,或者管道中都是相同模式的节点
                if (h == t || t.isData == isData) { // empty or same-mode
                    QNode tn = t.next;
                    // 尾节点被更改了,不一致读,重新轮询
                    if (t != tail)                  // inconsistent read
                        continue;
                    // 重新设置尾节点
                    if (tn != null) {               // lagging tail
                        advanceTail(t, tn);
                        continue;
                    }
					
					// 如果阻塞超时的话返回null
                    if (timed && nanos <= 0)        // can't wait
                        return null;
                    if (s == null)
                    	// 创建一个新的节点
                        s = new QNode(e, isData);
                    // 新节点设置为尾节点的下一个节点
                    if (!t.casNext(null, s))        // failed to link in
                        continue;
					// 新的节点设置为尾节点
                    advanceTail(t, s);              // swing tail and wait
                   	// 等待配对,后面分析这个方法
                    Object x = awaitFulfill(s, e, timed, nanos);
                    // 被取消了配对,那么就要清除该节点,后面分析该方法
                    if (x == s) {                   // wait was cancelled
                        clean(t, s);
                        return null;
                    }
					// s在队列中
                    if (!s.isOffList()) {           // not already unlinked
                        // 如果t是头节点,则头节点替换为s
                        advanceHead(t, s);          // unlink if head
                        // x不为空,说明当前操作是取数
                        if (x != null)              // and forget fields
                            s.item = s;
                        s.waiter = null;
                    }
                    return (x != null) ? (E)x : e;

                } else {                            // complementary-mode
                    // 进到这个分支是符合配对条件的,从头节点配对
                    QNode m = h.next;               // node to fulfill
                    // 不一致读,重新轮询
                    if (t != tail || m == null || h != head)
                        continue;                   // inconsistent read

                    Object x = m.item;
                    // 如下条件满足一个,则替换头节点
                    if (isData == (x != null) ||    // m 已经配对了
                        x == m ||                   // m 被取消了
                        !m.casItem(x, e)) {         // cas配对失败
                        advanceHead(h, m);          // 设置新的头节点,然后重新轮询
                        continue;
                    }

					// 走到这里说明成功配对,替换头节点为下一个节点
                    advanceHead(h, m);   
                    // 唤醒配对的节点           
                    LockSupport.unpark(m.waiter);
                    return (x != null) ? (E)x : e;
                }
            }
        }

	Object awaitFulfill(QNode s, E e, boolean timed, long nanos) {
            /* Same idea as TransferStack.awaitFulfill */
            final long deadline = timed ? System.nanoTime() + nanos : 0L;
            Thread w = Thread.currentThread();
            // 自旋次数
            int spins = ((head.next == s) ?
                         (timed ? maxTimedSpins : maxUntimedSpins) : 0);
            for (;;) {
                if (w.isInterrupted())
                    s.tryCancel(e);
                Object x = s.item;
                // x!=e 说明是配对成功,或者被取消了
                if (x != e)
                    return x;
                // 需要阻塞倒计时
                if (timed) {
                    nanos = deadline - System.nanoTime();
                    if (nanos <= 0L) {
                        s.tryCancel(e);
                        continue;
                    }
                }
                if (spins > 0)
                    --spins;
                else if (s.waiter == null)
                	// 等待线程为当前线程
                    s.waiter = w;
                else if (!timed)
                    LockSupport.park(this);
                else if (nanos > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanos);
            }
        }

	void clean(QNode pred, QNode s) {
            s.waiter = null; // forget thread
            // 轮询条件,s是pred的下一个节点
            while (pred.next == s) { // Return early if already unlinked
                QNode h = head;
                QNode hn = h.next;   // Absorb cancelled first node as head
                // 判断头节点是否被取消,如果取消就替换掉头节点,这里跟下方的延迟清除有关
                if (hn != null && hn.isCancelled()) {
                    advanceHead(h, hn);
                    continue;
                }
                // 尾节点
                QNode t = tail;      // Ensure consistent read for tail
                // 头尾节点相等说明队列为空
                if (t == h)
                    return;
                QNode tn = t.next;
                // 尾节点被修改了
                if (t != tail)
                    continue;
                // 有新的尾节点
                if (tn != null) {
                    advanceTail(t, tn);
                    continue;
                }
				// 有新的尾节点
                if (s != t) {        // If not tail, try to unsplice
                    QNode sn = s.next;
                    // sn等于s说明s节点被取消了,casNext成功的话说明清除s成功,
                    if (sn == s || pred.casNext(s, sn))
                        return;
                }
                // cleanMe可以理解为最近在哪个节点的后面入队列失败了,比如A是尾节点,B节点入队列失败了,那么cleanMe就是A节点。总之就是如果节点被取消了,那么cleanMe就是被取消节点的前一个节点
                QNode dp = cleanMe;
                if (dp != null) {    // Try unlinking previous cancelled node
                    QNode d = dp.next;
                    QNode dn;
                    // 如下4种情况需要把cleanMe重新设置为null,第4种情况其实d就是之前被取消的节点,需要清除掉
                    if (d == null ||               // 1.d为null 或者
                        d == dp ||                 // 2.d不在队列了 或者
                        !d.isCancelled() ||        // 3.d没有被取消 或者
                        (d != t &&                 // 4.d不是尾节点
                         (dn = d.next) != null &&  //   并且d的后继节点不为空
                         dn != d &&                //   并且d在队列中
                         dp.casNext(d, dn)))       //   并且清除d成功,
                        casCleanMe(dp, null);	
                    // 如果cleanMe节点是pred,那么就可以返回了    
                    if (dp == pred)
                        return;      // s is already saved node
                } else if (casCleanMe(null, pred))
                	// 到这里其实是没有清除掉这个取消的节点,而是做了延迟清除,在前面代码里做了清除,从头节点开始清的
                    return;          // Postpone cleaning s
            }
        }

三、总结SynchronousQueue的特质

  1. 对列没有容量的概念,也就是无界的,调用size()方法返回0。
  2. 不能删除节点,remove()方法都是返回false。
  3. peek()方法永远都是返回null。这也好理解,队列中保存的只是操作本身,而不是通常意义上的节点。
  4. 迭代器方法返回都是空的迭代器。
  5. 出入队列没有用加锁机制,直接用CAS完成。
  6. put()方法和take()方法,如果配对不成功的话会一直阻塞挂起,直到配对成功。