概述
在dual queue中,每个节点必须以原子方式持有它们的matchStatus,我们的实现如下:对于数据节点,通过cas将item字段从一个具体的value值转变为null,反之对于请求节点也是一样,通过cas将item字段从null转变为具体的value值。相比朴素的M&S queue,dual queue对enqueue和dequeue操作要求额外的原子保护,也因此它实现了更低成本的队列维护机制。
一旦一个节点被匹配上,它的matchStatus不会再发生改变。因此,我们可以安排它们的链表包含零个或多个匹配节点的前缀,后跟一个零个或多个不匹配节点的后缀。 (注意我们允许前缀和后缀都是零长度,反过来意味着我们不使用伪head节点)。
如果我们不关心时间和空间效率,我们可以执行出队和入队操作,靠从初始化的节点开始遍历:通过cas设置未匹配节点的item字段为match。
操作最多由以下3个方法实现,分别是:xfer()方法、tryAppend()方法、awaitMatch()方法
1、尝试去匹配现有节点
从head节点开始,跳过已经匹配成功的节点,直到找到相反模式的未匹配节点。如果cas未命中,循环重试前进2步,直到成功或者slack最多2个。通过要求每个重试前进2步,我们保证slack不会无限成长。遍历也会检查head节点是否是off-list状态,从而确定是否从新的head节点开始。
2、尝试追加一个新的节点(tryAppend方法)
从当前的tail指针开始,找到实际有效的last节点,并追加一个新的节点(如果head节点为null,构建第一个节点)。一个节点只有在它的前继节点匹配成功或者在相同模式下,才能追加成功。
3、等待匹配或者取消(awaitMatch方法)
阻塞等待另一个线程去匹配节点,也可以在当前线程被中断后或者阻塞等待超过规定的时间后取消等待。在多核处理器中,我们可以使用front-of-queue spinnning:如果一个节点看上去是队列中第一个未匹配的节点,它在阻塞前自旋一下。无论哪种情况,在阻塞前,它不会拼接当前head节点和第一个未匹配节点之间的任何节点。
front-of-queue spinnning大幅提升了高竞争队列的性能。spin thread在检查他们的中断状态过程中,会生成一个thread-local类型的随机数,该随机数用以决定是否执行thread.yield方法。thread.yield不一定有用,我们只能保证,在系统繁忙的自旋时,thread.yield可能帮的上忙,但一定不会有害。
xfer方法中的how参数
- NOW:立即返回,也不会插入节点
- SYNC:插入一个item为e(isData = haveData)到队列的尾部,然后自旋或阻塞当前线程直到节点被匹配或者取消。
- ASYNC:插入一个item为e(isData = haveData)到队列的尾部,不阻塞直接返回。
- TIMED:插入一个item为e(isData = haveData)到队列的尾部,然后自旋或阻塞当前线程直到节点被匹配或者取消或者超时。
//用于无超时的poll、tryTransfer方法
private static final int NOW = 0; // for untimed poll, tryTransfer
//用于offer、put、add方法
private static final int ASYNC = 1; // for offer, put, add
//用于transfer、take方法
private static final int SYNC = 2; // for transfer, take
//用于有超时的poll、tryTransfer方法
private static final int TIMED = 3; // for timed poll, tryTransfer
put方法
/**
* Inserts the specified element at the tail of this queue.
* As the queue is unbounded, this method will never block.
*
* @throws NullPointerException if the specified element is null
*/
public void put(E e) {
xfer(e, true, ASYNC, 0);
}
take方法
public E take() throws InterruptedException {
E e = xfer(null, false, SYNC, 0);
if (e != null)
return e;
Thread.interrupted();
throw new InterruptedException();
}
xfer方法
实现所有队列的入队出队方法
/**
* Implements all queuing methods. See above for explanation.
*
* @param e the item or null for take 如果是出队操作为null
* @param haveData true if this is a put, else a take 如果是入队操作为true,否则为false
* @param how NOW, ASYNC, SYNC, or TIMED
* @param nanos timeout in nanosecs, used only if mode is TIMED
* @return an item if matched, else e
* @throws NullPointerException if haveData mode but e is null
*/
private E xfer(E e, boolean haveData, int how, long nanos) {
//如果是入队操作但item字段为null,抛出空指针异常
if (haveData && (e == null))
throw new NullPointerException();
Node s = null; // the node to append, if needed
//标识retry
retry:
for (;;) { // restart on append race
//从head节点开始匹配
for (Node h = head, p = h; p != null;) { // find & match first node
boolean isData = p.isData;
Object item = p.item;
//如果节点处于未匹配状态
//data node处于未匹配状态的判定条件是:item!=null, isData为true
//request node处于未匹配状态的判定条件是:item==null,isData为false
//综上所述,节点处于未匹配状态的判定条件是:(item != null) == isData
if (item != p && (item != null) == isData) { // unmatched
//判断节点与操作类型是否匹配
//匹配的判定条件是:该操作是一个入队操作(haveData为true),而节点刚好是一个request node(isData为false)
//或者该操作是一个出队操作(haveData为false),而节点刚好是一个data node(isData为true)
if (isData == haveData) // can't match
break;
//如果节点与操作类型能匹配,通过cas设置节点的item字段
if (p.casItem(item, e)) { // match
for (Node q = p; q != h;) {
//将当前匹配节点的后继节点设置为新的head节点
Node n = q.next; // update by 2 unless singleton
if (head == h && casHead(h, n == null ? q : n)) {
//将旧的head节点自连接
h.forgetNext();
break;
} // advance and retry
if ((h = head) == null ||
(q = h.next) == null || !q.isMatched())
break; // unless slack < 2
}
//匹配成功,唤醒阻塞的线程
LockSupport.unpark(p.waiter);
return LinkedTransferQueue.<E>cast(item);
}
}
//如果节点处于已匹配状态,向下寻找下一个节点
Node n = p.next;
p = (p != n) ? n : (h = head); // Use head if p offlist
}
//如果整个队列遍历完都没有找到匹配的节点,则进行相应的how处理
if (how != NOW) { // No matches available
if (s == null)
s = new Node(e, haveData);
//将节点s添加到队列的尾部并返回它的前继节点
Node pred = tryAppend(s, haveData);
if (pred == null)
continue retry; // lost race vs opposite mode
if (how != ASYNC)
return awaitMatch(s, pred, e, (how == TIMED), nanos);
}
return e; // not waiting
}
}
tryAppend()方法
/**
* Tries to append node s as tail.
*
* @param s the node to append
* @param haveData true if appending in data mode
* @return null on failure due to losing race with append in
* different mode, else s's predecessor, or s itself if no
* predecessor
*/
private Node tryAppend(Node s, boolean haveData) {
for (Node t = tail, p = t;;) { // move p to last node and append
Node n, u; // temps for reads of next & tail
if (p == null && (p = head) == null) {
if (casHead(null, s))
return s; // initialize
}
else if (p.cannotPrecede(haveData))
return null; // lost race vs opposite mode
else if ((n = p.next) != null) // not last; keep traversing
p = p != t && t != (u = tail) ? (t = u) : // stale tail
(p != n) ? n : null; // restart if off list
else if (!p.casNext(null, s))
p = p.next; // re-read on CAS failure
else {
if (p != t) { // update if slack now >= 2
while ((tail != t || !casTail(t, s)) &&
(t = tail) != null &&
(s = t.next) != null && // advance and retry
(s = s.next) != null && s != t);
}
return p;
}
}
}
awaitMatch()方法
/**
* Spins/yields/blocks until node s is matched or caller gives up.
*
* @param s the waiting node
* @param pred the predecessor of s, or s itself if it has no
* predecessor, or null if unknown (the null case does not occur
* in any current calls but may in possible future extensions)
* @param e the comparison value for checking match
* @param timed if true, wait only until timeout elapses
* @param nanos timeout in nanosecs, used only if timed is true
* @return matched item, or e if unmatched on interrupt or timeout
*/
private E awaitMatch(Node s, Node pred, E e, boolean timed, long nanos) {
final long deadline = timed ? System.nanoTime() + nanos : 0L;
Thread w = Thread.currentThread();
int spins = -1; // initialized after first item and cancel checks
ThreadLocalRandom randomYields = null; // bound if needed
for (;;) {
Object item = s.item;
if (item != e) { // matched
// assert item != s;
s.forgetContents(); // avoid garbage
return LinkedTransferQueue.<E>cast(item);
}
if ((w.isInterrupted() || (timed && nanos <= 0)) &&
s.casItem(e, s)) { // cancel
unsplice(pred, s);
return e;
}
if (spins < 0) { // establish spins at/near front
if ((spins = spinsFor(pred, s.isData)) > 0)
randomYields = ThreadLocalRandom.current();
}
else if (spins > 0) { // spin
--spins;
if (randomYields.nextInt(CHAINED_SPINS) == 0)
Thread.yield(); // occasionally yield
}
else if (s.waiter == null) {
s.waiter = w; // request unpark then recheck
}
else if (timed) {
nanos = deadline - System.nanoTime();
if (nanos > 0L)
LockSupport.parkNanos(this, nanos);
}
else {
LockSupport.park(this);
}
}
}
Nonblocking Concurrent Data Structures with Condition Synchronization
摘要