SynchronousQueue 同步队列

1、概述

第一次接触SynchronousQueue,是在java提供的四种线程池中,缓存线程池的源码实现中看到(缓存线程池允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM,不建议使用):

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

SynchronousQueue 一个阻塞队列。每一个put操作必须等待一个take操作,否则不能继续添加元素,反之亦然。 同步队列没有任何内部容量,甚至没有一个容量。 你无法窥视SynchronousQueue,因为仅当你尝试删除它时,该元素才存在。 你不能插入元素(使用任何方法),除非另一个线程试图将其删除; 你无法进行迭代,因为没有要迭代的内容。 队列的头部是第一个排队的插入线程试图添加到队列中的元素; 如果没有这样的排队线程,则没有元素可用于删除,并且poll()将返回null。 为了其他Collection方法(例如,contains)的目的,SynchronousQueue充当空集合。 此队列不允许空元素.

同步队列类似于CSP和Ada中使用的集合通道。 它们非常适合切换设计,在该设计中,在一个线程中运行的对象必须与在另一个线程中运行的对象同步,以便向其传递一些信息,事件或任务。

此类支持可选的公平性策略,用于订购正在等待的生产者和使用者线程。 默认情况下,不保证此排序。 但是,将公平性设置为true构造的队列将按FIFO顺序授予线程访问权限。

它支持公平访问队列。默认情况下线程采用非公平性策略访问队列。SynchronousQueue类只有两个构造方法:

public SynchronousQueue() {
    this(false);
}

public SynchronousQueue(boolean fair) {
    transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
}

通过有参构造方法可以创建公平性访问的SynchronousQueue,如果设置为true,则等待的线程会采用先进先出的顺序访问队列,如果设置为false,则等待的线程会采用先进后出的顺序访问队列,类似栈。

SynchronousQueue可以看成是一个传递者,负责把生产者线程处理的数据直接传递给消费者线程。队列本身并不存储任何元素,非常适合传递性场景。SynchronousQueue的吞吐量高于ArrayBlockingQueue和LinkedBlockingQueue。

2、源码分析

2.1 SynchronousQueue类的定义

SynchronousQueue类的定义如下:

public class SynchronousQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable

该类继承自AbstractQueue抽象类,并实现了BlockingQueue接口。

SynchronousQueue类使用了一个Transferer内部类来转移数据:

    /**
     * Shared internal API for dual stacks and queues.
     */
    abstract static class Transferer<E> {
        /**
         * Performs a put or take.
         *
         * @param e if non-null, the item to be handed to a consumer;
         *          if null, requests that transfer return an item
         *          offered by producer.
         * @param timed if this operation should timeout
         * @param nanos the timeout, in nanoseconds
         * @return if non-null, the item provided or received; if null,
         *         the operation failed due to timeout or interrupt --
         *         the caller can distinguish which of these occurred
         *         by checking Thread.interrupted.
         */
        abstract E transfer(E e, boolean timed, long nanos);
    }

该类的唯一一个transfer方法是通过参数e来区分调用方法的是一个生产者线程还是一个消费者线程,如果e为null,则说明这是一个消费者线程,比如一个take操作,如果e不为null,那么就是一个生产者线程,这个数据就是这个线程需要交付的数据,比如一个put操作。

2.2 TransferQueue 和 TransferStack

SynchronousQueue 采用队列 TransferQueue 来实现公平性策略,采用堆栈TransferStack 来实现非公平性策略,这两个类都继承了内部抽象类Transferer,同时 SynchronousQueue 的 put、take 操作都是委托这两个类来实现的。

2.2.1 TransferQueue

/** Dual Queue */
    static final class TransferQueue<E> extends Transferer<E> {

        /** Node class for TransferQueue. */
        static final class QNode {
            volatile QNode next;          // next node in queue
            volatile Object item;         // CAS'ed to or from null
            volatile Thread waiter;       // to control park/unpark
            final boolean isData;
        }

        /** Head of queue */
        transient volatile QNode head;
        /** Tail of queue */
        transient volatile QNode tail;
        /**
         * Reference to a cancelled node that might not yet have been
         * unlinked from queue because it was the last inserted node
         * when it was cancelled.
         */
        transient volatile QNode cleanMe;

        TransferQueue() {
            QNode h = new QNode(null, false); // initialize to dummy node.
            head = h;
            tail = h;
        }

TransferQueue继承自Transferer,它使用 QNode 队列作为交易媒介,来实现公平交易。

TransferQueue中主要有3个QNode对象:

  • head:队列首节点
  • tail:队列尾节点
  • cleanMe:指向一个被取消但是还没有从队列移除的节点

2.2.2 TransferStack

/** Dual stack */
    static final class TransferStack<E> extends Transferer<E> {
    
        /* Modes for SNodes, ORed together in node fields */
        /** Node represents an unfulfilled consumer */
        static final int REQUEST    = 0;
        /** Node represents an unfulfilled producer */
        static final int DATA       = 1;
        /** Node is fulfilling another unfulfilled DATA or REQUEST */
        static final int FULFILLING = 2;

        /** Returns true if m has fulfilling bit set. */
        static boolean isFulfilling(int m) { return (m & FULFILLING) != 0; }

        /** Node class for TransferStacks. */
        static final class SNode {
            volatile SNode next;        // next node in stack
            volatile SNode match;       // the node matched to this
            volatile Thread waiter;     // to control park/unpark
            Object item;                // data; or null for REQUESTs
            int mode;

            SNode(Object item) {
                this.item = item;
            }

        /** The head (top) of the stack */
        volatile SNode head;

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

TransferStack同样继承自Transferer,它使用栈作为交易媒介,来实现非公平交易,TransferStack使用SNode类来作为栈节点。节点主要有以下几种状态:

  • REQUEST:表示了一个请求交易但是没有得到匹配的消费者
  • DATA:表示一个请求交易但是没有交付数据的生产者
  • FULFILLING:表示正在进行交易的生产者或者消费者

2.3 take 和 put

SynchronousQueue的put、take操作都是调用TransferQueue或者TransferStack的transfer方法来实现的,我们先来看一下这两个方法:

/**
     * Adds the specified element to this queue, waiting if necessary for
     * another thread to receive it.
     *
     * @throws InterruptedException {@inheritDoc}
     * @throws NullPointerException {@inheritDoc}
     */
    public void put(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();
        if (transferer.transfer(e, false, 0) == null) {
            Thread.interrupted();
            throw new InterruptedException();
        }
    }
/**
     * Retrieves and removes the head of this queue, waiting if necessary
     * for another thread to insert it.
     *
     * @return the head of this queue
     * @throws InterruptedException {@inheritDoc}
     */
    public E take() throws InterruptedException {
        E e = transferer.transfer(null, false, 0);
        if (e != null)
            return e;
        Thread.interrupted();
        throw new InterruptedException();
    }

从源码中可以看到,这两个方法都会调用transfer方法,其中,put方法传递的是e参数,所以模式为数据(公平isData = true,非公平mode= DATA),而take方法传递的是null,所以模式为请求(公平isData = false,非公平mode = REQUEST)。我们下面看一看在公平与非公平模式下的transfer方法具体实现。

2.3.1 公平模式 TransferQueue中的transfer

E transfer(E e, boolean timed, long nanos) {
        QNode s = null; // constructed/reused as needed
        // 获取当前的节点模式,isData = true为put 
        boolean isData = (e != null);

        for (;;) {
            QNode t = tail;
            QNode h = head;
            // 队列没有初始化,自旋
            if (t == null || h == null)         // saw uninitialized value
                continue;                       // spin
            // 头尾节点相等(队列为null),或者当前节点和队列尾节点具有相同的交易类型
            // 将节点添加到队列尾部,并且等待匹配
            if (h == t || t.isData == isData) { // empty or same-mode
                QNode tn = t.next;
                // t != tail表明已有其他线程修改了tail,当前线程需要重新再来
                if (t != tail)                  // inconsistent read
                    continue;
                // 若尾节点的后继节点不为null,则表明已经有其他线程添加了节点,更新尾节点
                if (tn != null) {               // lagging tail
                    advanceTail(t, tn);
                    continue;
                }
                // 超时
                if (timed && nanos <= 0)        // can't wait
                    return null;
                // s == 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
                // 调用awaitFulfill方法,若节点是head.next,则进行自旋
                // 否则,直接阻塞,直到有其他线程与之匹配,或它自己进行线程的中断
                Object x = awaitFulfill(s, e, timed, nanos);
                // 若返回的x == s表示,当前线程已经超时或者中断,不然的话s == null或者是匹配的节点
                if (x == s) {                   // wait was cancelled
                    clean(t, s);
                    return null;
                }
                // 若s节点还没有从队列删除
                if (!s.isOffList()) {           // not already unlinked
                    // 尝试将s节点设置为head,移出t
                    advanceHead(t, s);          // unlink if head
                    if (x != null)              // and forget fields
                        s.item = s;
                    s.waiter = null;
                }
                return (x != null) ? (E)x : e;

            } else {                            // complementary-mode
                // 这里是从head.next开始,因为TransferQueue总是会存在一个dummy节点
                QNode m = h.next;               // node to fulfill
                // 不一致读,表明有其他线程修改了队列
                if (t != tail || m == null || h != head)
                    continue;                   // inconsistent read

                Object x = m.item;
                // isData == (x != null):判断isData与x的模式是否相同,相同表示已经匹配了
                // x == m :m节点被取消了
                // !m.casItem(x, e):如果尝试将数据e设置到m上失败
                if (isData == (x != null) ||    // m already fulfilled
                    x == m ||                   // m cancelled
                    !m.casItem(x, e)) {         // lost CAS
                    // 将m设置为头结点,h出列,然后重试
                    advanceHead(h, m);          // dequeue and retry
                    continue;
                }
                // 成功匹配了,m设置为头结点h出列,向前推进
                advanceHead(h, m);              // successfully fulfilled
                // 唤醒m的等待线程
                LockSupport.unpark(m.waiter);
                return (x != null) ? (E)x : e;
            }
        }
    }

该方法的主要运行过程如下:

1、如果队列为空,或者请求交易的节点和队列中的节点具有相同的交易类型,那么就将该请求交易的节点添加到队列尾部等待交易,直到被匹配或者被取消。

2、如果队列中包含了等待的节点,并且请求的节点和等待的节点是互补的,那么进行匹配并且进行交易。

当队列为空时,节点入列然后通过调用awaitFulfill()方法自旋,该方法主要用于自旋/阻塞节点,直到节点被匹配返回或者取消、中断:


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);
        // 如果线程进行了阻塞 -> 唤醒或者中断了,那么x != e 肯定成立,直接返回当前节点即可
        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)
            --spins;
        // 设置等待线程
        else if (s.waiter == null)
            s.waiter = w;
        // 设置没有超时地阻塞线程
        else if (!timed)
            LockSupport.park(this);
        // 设置具有超时地阻塞线程
        else if (nanos > spinForTimeoutThreshold)
            LockSupport.parkNanos(this, nanos);
    }

在自旋/阻塞过程中做了一点优化,就是判断当前节点是否为对头元素,如果是的则先自旋,如果自旋次数过了,则才阻塞,这样做的主要目的就在如果生产者、消费者立马来匹配了则不需要阻塞,因为阻塞、唤醒会消耗资源。在整个自旋的过程中会不断判断是否超时或者中断了,如果中断或者超时了则调用tryCancel()取消该节点。

取消过程就是将节点的item设置为自身(itemOffset是item的偏移量)。所以在调用awaitFulfill()方法时,如果当前线程被取消、中断、超时了那么返回的值肯定是s,否则返回的则是匹配的节点。如果返回值是节点s,那么if(x == s)必定成立,如下:

if (x == s) {  // wait was cancelled
    clean(t, s);
    return null;
}

如果返回的x == s成立,则调用clean()方法清理节点s:


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) { // 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;
            if (sn == s || pred.casNext(s, sn))
                return;
        }
        QNode dp = cleanMe;
        if (dp != null) {    // Try unlinking previous cancelled node
            QNode d = dp.next;
            QNode dn;
            if (d == null ||               // d is gone or
                d == dp ||                 // d is off list or
                !d.isCancelled() ||        // d not cancelled or
                (d != t &&                 // d not tail and
                    (dn = d.next) != null &&  //   has successor
                    dn != d &&                //   that is on list
                    dp.casNext(d, dn)))       // d unspliced
                casCleanMe(dp, null);
            if (dp == pred)
                return;      // s is already saved node
        } else if (casCleanMe(null, pred))
            return;          // Postpone cleaning s
    }

方法中的注释:

不论任何情况,列表上最后插入的节点不能被删除。为了适应这一点,如果我们不能删除s,我们将其前驱设置为“CleanMe”,先删除以前保存的版本。节点s或先前保存的节点中的至少一个总是可以被删除。

该方法的主要逻辑如下:

1、删除的节点不是queue尾节点, 这时直接以pred.casNext(s, s.next)方式来进行删除

2、删除的节点是队尾节点:

此时 cleanMe == null, 则 前继节点pred标记为 cleanMe, 为下次删除做准备
此时cleanMe != null,先删除上次需要删除的节点,然后将cleanMe置为null,让后再将pred赋值给cleanMe

2.3.2 非公平模式 TransferStack中的transfer

E transfer(E e, boolean timed, long nanos) {
    SNode s = null; // constructed/reused as needed
    // 获取当前节点的模式
    int mode = (e == null) ? REQUEST : DATA;
 
    for (;;) {
        SNode h = head;
        // 栈为空或者当前节点模式与头节点模式一样,将节点压入栈内,等待匹配
        if (h == null || h.mode == mode) {  // empty or same-mode
            // 超时
            if (timed && nanos <= 0) {      // can't wait
                // 节点被取消了,弹出被取消的节点
                if (h != null && h.isCancelled())
                    casHead(h, h.next);     // pop cancelled node
                else
                    return null;
            } 
            // 未超时,创建SNode节点
            else if (casHead(h, s = snode(s, e, h, mode))) {
                // 自旋,等待匹配
                SNode m = awaitFulfill(s, timed, nanos);
                // 返回的m == s 表示该节点被取消了或者超时、中断了
                if (m == s) {               // wait was cancelled
                    // 清理节点s,返回null
                    clean(s);
                    return null;
                }
 
                // 因为通过前面一步将s替换成了head,如果h.next == s,则表示有其他节点插入到s前面了,变成了head
                // 且该节点就是与节点s匹配的节点
                if ((h = head) != null && h.next == s)
                    casHead(h, s.next);     // help s's fulfiller
                // 如果是请求则返回匹配的域,否则返回节点s的域
                return (E) ((mode == REQUEST) ? m.item : s.item);
            }
        } 
        // 如果栈不为null,且两者模式不匹配(h != null && h.mode != mode)
        // 说明他们是一队对等匹配的节点,尝试用当前节点s来满足h节点
        else if (!isFulfilling(h.mode)) { // try to fulfill
            // head 节点已经取消了,向前推进
            if (h.isCancelled())            // already cancelled
                casHead(h, h.next);         // pop and retry
            // 尝试将当前节点打上“正在匹配”的标记,并设置为head
            else if (casHead(h, s=snode(s, e, h, FULFILLING|mode))) {
                for (;;) { // loop until matched or waiters disappear
                    // s为当前节点,m是s的next节点,
                    // m节点是s节点的匹配节点
                    SNode m = s.next;       // m is s's match
                    // m == null,其他节点把m节点匹配走了
                    if (m == null) {        // all waiters are gone
                        // 将s弹出
                        casHead(s, null);   // pop fulfill node
                        // 将s置空,下轮循环的时候还会新建
                        s = null;           // use new node next time
                        break;              // restart main loop
                    }
                    // 获取m的next节点
                    SNode mn = m.next;
                    // 尝试匹配
                    if (m.tryMatch(s)) {
                        // 匹配成功,将s、m弹出
                        casHead(s, mn);     // pop both s and m
                        return (E) ((mode == REQUEST) ? m.item : s.item);
                    } else                  // lost match
                        // 如果没有匹配成功,说明有其他线程已经匹配了,把m移出
                        s.casNext(m, mn);   // help unlink
                }
            }
        } 
        // 到这最后一步说明节点正在匹配阶段
        else {                            // help a fulfiller
            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
            }
        }
    }
}

该方法的主要逻辑如下:

1、如果当前的交易栈是空的,或者包含与请求交易节点模式相同的节点,那么就将这个请求交易的节点作为新的栈顶节点,等待被下一个请求交易的节点匹配,最后会返回匹配节点的数据或者null,如果被取消则会返回null。

2、如果当前交易栈不为空,并且请求交易的节点和当前栈顶节点模式互补,那么将这个请求交易的节点的模式变为FULFILLING,然后将其压入栈中,和互补的节点进行匹配,完成交易之后将两个节点一起弹出,并且返回交易的数据。

3、如果栈顶已经存在一个模式为FULFILLING的节点,说明栈顶的节点正在进行匹配,那么就帮助这个栈顶节点快速完成交易,然后继续交易。

当节点加入栈内后,通过调用awaitFulfill()方法自旋等待节点匹配:

SNode awaitFulfill(SNode s, boolean timed, long nanos) {
    // 计算超时时间点
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
    // 获取当前线程对象
    Thread w = Thread.currentThread();
 
    // 自旋次数
    // shouldSpin 用于检测当前节点是否需要自旋
    // 如果栈为空、该节点是首节点或者该节点是匹配节点,则先采用自旋,否则阻塞
    int spins = (shouldSpin(s) ?
            (timed ? maxTimedSpins : maxUntimedSpins) : 0);
    for (;;) {
        // 线程中断了,取消该节点
        if (w.isInterrupted())
            s.tryCancel();
 
        // 匹配节点
        SNode m = s.match;
 
        // 如果匹配节点m不为空,则表示匹配成功,直接返回
        if (m != null)
            return m;
        // 超时
        if (timed) {
            nanos = deadline - System.nanoTime();
            // 节点超时,取消
            if (nanos <= 0L) {
                s.tryCancel();
                continue;
            }
        }
 
        // 每次自旋的时候都需要检查自身是否满足自旋条件,满足就 - 1,否则为0
        if (spins > 0)
            spins = shouldSpin(s) ? (spins-1) : 0;
 
        // 第一次阻塞时,会将当前线程设置到s上
        else if (s.waiter == null)
            s.waiter = w;
 
        // 阻塞当前线程
        else if (!timed)
            LockSupport.park(this);
        // 超时
        else if (nanos > spinForTimeoutThreshold)
            LockSupport.parkNanos(this, nanos);
    }
}

这个线程一直阻塞直到被匹配,在阻塞之前首先会自旋,这个自旋会在阻塞之前进行,它会调用shouldSpin方法来进行判断是否需要自旋,下面展示了shouldSpin这个方法:

boolean shouldSpin(SNode s) {
    SNode h = head;
    return (h == s || h == null || isFulfilling(h.mode));
}

如果当前节点在栈顶,并且正在请求交易,那么就应该自旋。在多CPU的环境下,这种情况下的自旋是有必要的,因为很可能立刻就会有新的线程到来,那么就会立刻进行交易而不需要进行阻塞,然后被唤醒,这是需要过程的,所以这样的自旋等待是值得的。

若线程被中断了,则调用tryCancel()方法取消该节点:

void tryCancel() {
    UNSAFE.compareAndSwapObject(this, matchOffset, null, this);
}

该过程和TransferQueue相同。awaitFullfill()方法如果返回的m == s,则表示当前节点已经中断取消了,则需要调用clean()方法,清理节点s:

void clean(SNode s) {
    s.item = null;   // forget item
    s.waiter = null; // forget thread
 
    SNode past = s.next;
    if (past != null && past.isCancelled())
        past = past.next;
 
    // Absorb cancelled nodes at head
    SNode p;
    while ((p = head) != null && p != past && p.isCancelled())
        casHead(p, p.next);
 
    // Unsplice embedded nodes
    while (p != null && p != past) {
        SNode n = p.next;
        if (n != null && n.isCancelled())
            p.casNext(n, n.next);
        else
            p = n;
    }
}

clean()方法就是将head节点到s节点之间所有已经取消的节点全部移出。

3、应用场景和总结

应用场景:

因为SynchronousQueue没有存储功能,因此put和take会一直阻塞,直到有另一个线程已经准备好参与到交付过程中。仅当有足够多的消费者,并且总是有一个消费者准备好获取交付的工作时,才适合使用同步队列。

例如概述中说的:
Executors.newCachedThreadPool()

 public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>(),
                                      threadFactory);
    }

由于ThreadPoolExecutor内部实现任务提交的时候调用的是工作队列(BlockingQueue接口的实现类)的非阻塞式入队列方法(offer方法),因此,在使用SynchronousQueue作为工作队列的前提下,客户端代码向线程池提交任务时,而线程池中又没有空闲的线程能够从SynchronousQueue队列实例中取一个任务,那么相应的offer方法调用就会失败(即任务没有被存入工作队列)。此时,ThreadPoolExecutor会新建一个新的工作者线程用于对这个入队列失败的任务进行处理(假设此时线程池的大小还未达到其最大线程池大小)。

所以,使用SynchronousQueue作为工作队列,工作队列本身并不限制待执行的任务的数量。但此时需要限定线程池的最大大小为一个合理的有限值,而不是Integer.MAX_VALUE,否则可能导致线程池中的工作者线程的数量一直增加到系统资源所无法承受为止。

1、线程做相同类型的操作:

  • 多个线程 take() ,则将线程包装成QNode节点,item为null,将节点添加到队列,将线程挂起;

  • 多个线程 put() ,将线程包装成QNode节点,item为 e,将节点添加到队列,将线程挂起。

2、线程做不同类型的操作:

  • 有线程先做了put() ,其他线程做take() 操作时,take取到队列中的第一个等待节点中的item,take返回item,并将第一个等待节点唤醒,put返回e;

  • 有线程先做了take() ,其他线程put() 操作时,put将元素e赋值给队列中第一个等待节点的item,put返回e,并将第一个等待节点唤醒,take返回e。

举个不太恰当的例子便于理解,我们可以假设出一个男女配对的场景,比如男指的是take,女指的是put

1、一个男的过来,如果一个人都没有,那么他需要等待;如果发现有一堆男的在等待,那么他需要排到队列后面;如果发现是一堆女的在排队,那么他直接牵走队头的那个女的;

2、相反一个女的过来,如果一个人都没有,那么她需要等待;如果发现有一堆女的在等待,那么她需要排到队列后面;如果发现是一堆男的在排队,那么队头的那个男的直接出队牵走这个女的;

©️2020 CSDN 皮肤主题: 黑客帝国 设计师:上身试试 返回首页