简介
SynchronousQueue是一个不存储元素的队列。每一个put操作必须等待一个take操作,否则不能继续添加元素。
它支持公平访问队列。默认情况下线程采用非公平性策略访问队列。SynchronousQueue类只有两个构造方法:
public SynchronousQueue() { this(false); } public SynchronousQueue(boolean fair) { transferer = fair ? new TransferQueue<E>() : new TransferStack<E>(); }
使用第二个构造方法可以创建公平性访问的SynchronousQueue,如果设置为true,则等待的线程会采用先进先出的顺序访问队列。
SynchronousQueue可以看成是一个传球手,负责把生产者线程处理的数据直接传递给消费者线程。队列本身并不存储任何元素,非常适合传递性场景。SynchronousQueue的吞吐量高于ArrayBlockingQueue和LinkedBlockingQueue。
SynchronousQueue源码详解
SynchronousQueue类的定义如下:
public class SynchronousQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable
该类同样继承自AbstractQueue抽象类,并实现了BlockingQueue接口,这里不再叙述。
SynchronousQueue类使用了一个非常关键的内部类来转移数据:
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操作。
SynchronousQueue采用队列TransferQueue来实现公平性策略,采用堆栈TransferStack来实现非公平性策略,SynchronousQueue的put、take操作都是委托这两个类来实现的,我们下面先来了解一下这两个类。
TransferQueue
TransferQueue继承自Transferer:
static final class TransferQueue<E> extends Transferer<E>
它使用队列作为交易媒介,来实现公平交易,TransferQueue使用QNode类来作为队列节点:
static final class QNode { // 指向下一个节点 volatile QNode next; // next node in queue // item数据项 volatile Object item; // CAS'ed to or from null // 等待线程 volatile Thread waiter; // to control park/unpark // 是否为数据的标识 final boolean isData; ... }
TransferQueue类中主要有3个QNode的对象:
/** 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;
- head:队列首节点
- tail:队列尾节点
- cleanMe:指向一个被取消但是还没有从队列移除的节点
同时,对于TransferQueue需要注意的是,其队列永远都存在一个dummy node,在构造时创建:
TransferQueue() { QNode h = new QNode(null, false); // initialize to dummy node. head = h; tail = h; }
TransferStack
TransferStack同样继承自Transferer:
static final class TransferStack<E> extends Transfer
它使用栈作为交易媒介,来实现非公平交易,TransferStack使用SNode类来作为栈节点:
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 // item数据线 Object item; // data; or null for REQUESTs // 节点状态 int mode; ... }
节点主要有以下几种状态:
/** 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;
- REQUEST:表示了一个请求交易但是没有得到匹配的消费者
- DATA:表示一个请求交易但是没有交付数据的生产者
- FULFILLING:表示正在进行交易的生产者或者消费者
SynchronousQueue的put、take操作都是调用TransferQueue或者TransferStack的transfer方法来实现的,我们先来看一下这两个方法:
public void put(E e) throws InterruptedException { // 若插入的数据是null,则直接抛出NullPointerException异常 if (e == null) throw new NullPointerException(); // 调用transfer方法 if (transferer.transfer(e, false, 0) == null) { Thread.interrupted(); throw new InterruptedException(); } }
public E take() throws InterruptedException { // 调用transfer方法 E e = transferer.transfer(null, false, 0); // 若值不为null,则直接返回 if (e != null) return e; Thread.interrupted(); throw new InterruptedException(); }
从源码中可以看到,这两个方法都会调用transfer方法,其中,put方法传递的是e参数,所以模式为数据(公平isData = true,非公平mode= DATA),而take方法传递的是null,所以模式为请求(公平isData = false,非公平mode = REQUEST)。我们下面看一看在公平与非公平模式下的transfer方法具体实现。
公平模式
TransferQueue实现的transfer方法如下:
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 // 头尾节点相等(队列为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; } // 这里是从head.next开始,因为TransferQueue总是会存在一个dummy节点 else { // complementary-mode 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()取消该节点。
void tryCancel(Object cmp) { UNSAFE.compareAndSwapObject(this, itemOffset, cmp, this); }
取消过程就是将节点的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,我们将其前驱设置为“CurrMe”,先删除以前保存的版本。节点s或先前保存的节点中的至少一个总是可以被删除。
该方法的主要逻辑如下:
1、删除的节点不是queue尾节点, 这时直接以pred.casNext(s, s.next)方式来进行删除
2、删除的节点是队尾节点:
- 此时 cleanMe == null, 则 前继节点pred标记为 cleanMe, 为下次删除做准备
- 此时cleanMe != null,先删除上次需要删除的节点,然后将cleanMe置为null,让后再将pred赋值给cleanMe
非公平模式
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节点之间所有已经取消的节点全部移出。
SynchronousQueue的实现有点难,看不懂啊。。。