我的原则:先会用再说,内部慢慢来。
学以致用,根据场景学源码
文章目录
- 一、前言
- 二、架构
- 2.1 UML 图
- 2.2 TransferStack流程图
- 2.3 TransferQueue流程图
- 2.4 特性
- 2.5 SynchronousQueue 与 AbstractQueuedSynchronizer 的关系
- 三、SynchronousQueue 剖析
- 四、 TransferStack 栈(队列出入非公平,先进后出,常用)
- 4.1 SNode 节点
- 4.2 transfer 方法
- 4.3 casHead 方法
- 4.4 isCancelled 方法
- 4.5 isFulfilling 方法
- 4.6 tryMatch 方法
- 4.7 awaitFulfill 方法
- 4.8 shouldSpin 方法
- 4.9 maxTimedSpins 变量与 maxUntimedSpins 变量
- 4.10 spinForTimeoutThreshold 变量
- 4.11 tryCancel 方法
- 4.12 clean 方法
- 五、TransferQueue 栈(队列出入公平,先进先出)
- 六、总结
- 七、番外篇
一、前言
二、架构
2.1 UML 图
2.2 TransferStack流程图
2.2.1 节点匹配流程图
2.2.2 transfer 流程图
2.2.3 awaitFulfill 流程图
2.3 TransferQueue流程图
2.3.1 节点匹配流程图
2.3.2 TransferQueue#transfer 流程图
2.3.3 TransferQueue#clean 流程图
2.4 特性
- 生产1次,消费1次,再生产,再消费,否则堵塞。
- SynchronousQueue 既继承了 AbstractQueue抽象类又实现了 BlockingQueue,跟 LinkedBlockingQueue 一样
public class SynchronousQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
private static final long serialVersionUID = -3223113410248163686L;
- Java 6的SynchronousQueue的实现采用了一种性能更好的无锁算法 — 扩展的“Dual stack and Dual queue”算法。
2.5 SynchronousQueue 与 AbstractQueuedSynchronizer 的关系
- AbstractQueuedSynchronizer 内部其实是加了锁 Reentrantlock,SynchronousQueue 内部不加锁,先 spin 自旋,不行就去 LockSupport.park(this); 阻塞
三、SynchronousQueue 剖析
3.1 构造方法
private transient volatile Transferer<E> transferer;
public SynchronousQueue() {
this(false);
}
public SynchronousQueue(boolean fair) {
transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
}
- 默认非公平锁
- 竞争机制支持公平和非公平两种:非公平竞争模式使用的数据结构是后进先出栈(Lifo Stack);公平竞争模式则使用先进先出队列(Fifo Queue),性能上两者是相当的,一般情况下,Fifo通常可以支持更大的吞吐量,但Lifo可以更大程度的保持线程的本地化。
3.2 take 方法 (取元素)
public E take() throws InterruptedException {
E e = transferer.transfer(null, false, 0);
if (e != null)
return e;
Thread.interrupted();
throw new InterruptedException();
}
3.3 put 方法 (插元素)
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();
}
}
3.4 内部静态抽象类 Transferer
abstract static class Transferer<E> {
abstract E transfer(E e, boolean timed, long nanos);
}
3.5 take 与 put 内部区别
两者底层都是调用了 transferer.transfer(E e, boolean timed, long nanos); 方法,区别在于:
- 第一个参数E e,,若是元素 e,表示是要入队列 put,若是 null,表示是要取数据 take。
- 第二个参数 boolean timed,即是否会超时,若是 true,表明是在一定时间内尝试,若是 false,则表示一直尝试,永不退出。
- 第三个参数long nanos ,表示尝试多久,若第二个参数是 false,这个参数就是 0.
3.6 变量 cleanMe
transient volatile QNode cleanMe;
- 节点存在的作用是:标记它的下个节点要删除
- 何时使用:当你要删除节点 node, 若节点 node 是队列的末尾, 则开始用这个节点
- 为什么要这样呢?
因为删除一个节点直接 A.CASNext(B, B.next) 就可以,但是当节点 B 是整个队列中的末尾元素时, 一个线程删除节点B, 一个线程在节点B之后插入节点这样操作容易致使插入的节点丢失, 这个cleanMe很像ConcurrentSkipListMap 中的删除添加的 marker 节点。
四、 TransferStack 栈(队列出入非公平,先进后出,常用)
- 根据 debug 学习法,咱们直接看TransferStack 的 transfer 方法
4.1 SNode 节点
static final class SNode {
// next指向栈中下一个元素
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;
- 匹配流程图
4.2 transfer 方法
transfer 流程图
E transfer(E e, boolean timed, long nanos) {
// 既然是 queue 队列,内部肯定都是用 Node 包装
SNode s = null;
/*
mode 模式(操作):
若是 null,那么是 take 操作,也就是 REQUES
若是元素 e,那么是 put 操作,也就是 DATA
*/
int mode = (e == null) ? REQUEST : DATA;
for (;;) {
// 这个 h = head, 一开始肯定是 null
SNode h = head;
// 要么 head 是 null,要么队列中 mode一样,全都是 take 或者全都是 put,进入下面的 if
if (h == null || h.mode == mode) { // empty or same-mode
/*
timed = true :表示如果有规定超时 timeout 时间。
nanos <= 0 : 表示到点了,该回家了
*/
if (timed && nanos <= 0) { // can't wait
if (h != null && h.isCancelled())
// 节点已经timeout 取消了,pop出 head节点
casHead(h, h.next); // pop cancelled node
else
return null;
/*
put 的时候,把 element 塞到 head 的位置(往头部插入)
cas 设置头节点 head 是 s (s节点包装了入queue 的 e)
*/
} else if (casHead(h, s = snode(s, e, h, mode))) {
/*
等待结束,也就是put的时候,阻塞在这里等被 take,
也就是 take 的时候,阻塞在这里等被 put.
这个方法重点讲。
// 通过awaitFulfill方法自旋阻塞找到匹配操作的节点
*/
SNode m = awaitFulfill(s, timed, nanos);
if (m == s) { // wait was cancelled,说明已经超时退出了
clean(s); // 清理 s 节点
return null; // 返回 null
}
if ((h = head) != null && h.next == s)
casHead(h, s.next); // help s's fulfiller
return (E) ((mode == REQUEST) ? m.item : s.item);
}
/*
还未完成任务 fulfill
能跑到这个if,说明head 队列是 take,突然来了个 put ,或者head 队列是 put,突然来了个 take
*/
} else if (!isFulfilling(h.mode)) { // try to fulfill
if (h.isCancelled()) // already cancelled
casHead(h, h.next); // pop and retry
/*
这个地方如果是取 take 的话,那么 s节点内是 null
首先把 take 的 Node 插入 head,然后此时 head 后面假如有 2个 put 的Node阻塞着。
*/
else if (casHead(h, s=snode(s, e, h, FULFILLING|mode))) {
for (;;) { // loop until matched or waiters disappear
// 接上面,这个地方 m = s.next ,也就是阻塞的 put Node
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
}
// mn 是下一个 Node
SNode mn = m.next;
// 尝试匹配,也就是一个 put 匹配一个 take,匹配上的话,俩俩牵走。匹配成功进入
if (m.tryMatch(s)) {
// 把 s 和 m 都拿掉,因为他们牵走走了。所以把 head 设置成 mn,也就是第3个
casHead(s, mn); // pop both s and m
return (E) ((mode == REQUEST) ? m.item : s.item);
} else // lost match
/*
如果匹配不成功,那么拿掉m节点。继续进入 for
匹配不成功是因为在那一瞬间被别人抢走了
*/
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
}
}
}
}
4.3 casHead 方法
- java.util.concurrent.SynchronousQueue.TransferStack#casHead 方法
boolean casHead(SNode h, SNode nh) {
return h == head &&
UNSAFE.compareAndSwapObject(this, headOffset, h, nh);
}
- sun.misc.Unsafe#compareAndSwapObject 方法
public final native boolean compareAndSwapObject(Object o, long offset,
Object expected,
Object x);
- 这个简单,只是利用底层的 CAS,把head节点给换了
4.4 isCancelled 方法
- java.util.concurrent.SynchronousQueue.TransferStack.SNode#isCancelled 方法
- 取消操作(被外部中断或者超时):match == this;
boolean isCancelled() {
return match == this;
}
4.5 isFulfilling 方法
/** 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;
static boolean isFulfilling(int m) { return (m & FULFILLING) != 0; }
- 这个方法只有当m = FULFILLING 的时候,才返回 true
4.6 tryMatch 方法
-
上游
-
尝试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置空
waiter = null;
LockSupport.unpark(w);
}
return true;
}
// 判断当前节点是否已与s进行匹配
return match == s;
}
4.7 awaitFulfill 方法
- java.util.concurrent.SynchronousQueue.TransferStack#awaitFulfill 方法
- 未设置操作时间同时未被外部线程中断则需阻塞等待匹配节点唤醒当前阻塞的线程
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节点的match设置为s自己,这样判断的时候就知道这个节点是被取消的
s.tryCancel();
SNode m = s.match;
// match非空则表示当前节点已经被匹配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;
// 未设置超时,直接阻塞
else if (!timed)
LockSupport.park(this);
// 设置超时时间阻塞
// 阻塞时间是否超过1000nm纳秒?如果是的话,一直自旋台耗CPU了,还是老老实实去睡觉吧。
else if (nanos > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanos);
}
}
4.8 shouldSpin 方法
- java.util.concurrent.SynchronousQueue.TransferStack#shouldSpin 方法
- 判断是否需要自旋操作,3种情况需要自旋
- 栈顶节点等于s节点
- 栈顶节点为空
- 栈顶节点为已和其他节点匹配的节点(mode = FULFILLING|mode)
boolean shouldSpin(SNode s) {
SNode h = head;
return (h == s || h == null || isFulfilling(h.mode));
}
4.9 maxTimedSpins 变量与 maxUntimedSpins 变量
static final int NCPUS = Runtime.getRuntime().availableProcessors();
static final int maxTimedSpins = (NCPUS < 2) ? 0 : 32;
static final int maxUntimedSpins = maxTimedSpins * 16;
- maxTimedSpins 变量
- 如果cpu个数是1,那么自旋spin 0次,直接阻塞
- 如果cpu个数大于1 ,那么自旋32次。
- maxUntimedSpins 变量( = maxTimedSpins * 16 = 512)
- 解析下这个语句(位于 awaitFulfill 方法):
int spins = (shouldSpin(s) ?
(timed ? maxTimedSpins : maxUntimedSpins) : 0);
- 计算自旋次数
- 如果不需要自旋,那么直接 spins = 0
- 如果需要自旋,并且会超时,那么 spins = maxTimedSpins = 32
- 如果需要自旋,并且不会超时,那么 spins = maxUntimedSpins = 512
4.10 spinForTimeoutThreshold 变量
static final long spinForTimeoutThreshold = 1000L;
- 阈值,1000 ns纳秒
- 解析下这个语句(位于 awaitFulfill 方法):
...
// 超时配置处理
if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
s.tryCancel();
continue;
}
}
...
// 自旋spins
if (spins > 0)
spins = shouldSpin(s) ? (spins-1) : 0;
// 未设置超时,直接阻塞
else if (!timed)
LockSupport.park(this);
// 设置超时时间阻塞
else if (nanos > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanos);
...
- timed = true ,也就是有 timeout 情况,如果 nanos <= 0L ,那么说明已经等的够久了,s.tryCancel(); 取消此次操作
- 计算超时次数 ,可以知道在有 timeout的情况下,spins = 32,也就是自旋32次之后,spins 会递减到 0 ,紧接着走 else if (nanos > spinForTimeoutThreshold)
- 如果此时的超时时间仍旧还大于 1000 ns纳秒,那么就直接调用 LockSupport.parkNanos(this, nanos);避免过多的自旋 spin影响cpu 性能
4.11 tryCancel 方法
void tryCancel() {
UNSAFE.compareAndSwapObject(this, matchOffset, null, this);
}
- 实质是设置变量 match = this
4.12 clean 方法
- java.util.concurrent.SynchronousQueue.TransferStack#clean 方法
/**
* Unlinks s from the stack.
*/
void clean(SNode s) {
// item,waiter 置空
s.item = null; // forget item
s.waiter = null; // forget thread
// s的下一个节点处于取消操作状态,则past指向past的下一个节点
SNode past = s.next;
if (past != null && past.isCancelled())
past = past.next;
// 头节点被取消操作则进行将next节点更新为头节点
/*
* 这里说一下为什么要:p != past ,因为如果 past == p = head的话,
* past就是头节点head,那么待会直接走下面的 while 就行,不用在这里浪费时间,直接跳过这一步。
*/
SNode p;
while ((p = head) != null && p != past && p.isCancelled())
casHead(p, p.next);
// 头节点调整完毕,现在将栈节点中每个节点都会进行检查一遍,更新前后节点的关系,将取消操作的节点进行排除
while (p != null && p != past) {
SNode n = p.next;
if (n != null && n.isCancelled())
p.casNext(n, n.next);
else
p = n;
}
}
- 这个clean 操作,不仅仅会把当前的 SNode 内部清空,也会将整个栈内失效的节点给清除掉。
五、TransferQueue 栈(队列出入公平,先进先出)
- 根据 debug 学习法,咱们直接看 TransferQueue 的 transfer 方法
- 插尾巴 tail ,从头head 取
5.1 QNode 节点
- 匹配流程图
//队列节点定义
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;
}
5.2 transfer 方法
transfer 流程图
E transfer(E e, boolean timed, long nanos) {
// 注意,queue结构的是 QNode,Stack结构的是 SNode
QNode s = null; // constructed/reused as needed
// 判断操作是 take 还是 put
boolean isData = (e != null);
for (;;) {
QNode t = tail;
QNode h = head;
// 初始化 TransferQueue 的时候, head 与 tail 非 null
if (t == null || h == null) // saw uninitialized value
continue; // spin
// h == t 说明队列里面压根就没元素,只有一个假节点 QNode h = new QNode(null, false);
// t.isData == isData 说明队列内的元素与新进来的类型一致。记住是 tail
if (h == t || t.isData == isData) { // empty or same-mode
QNode tn = t.next;
// 确保 t 没问题
if (t != tail) // inconsistent read
continue;
// tn 按理来说必须是 null,因为 tail.next 尾巴之后肯定没元素了,如果此时有,那么就是刚插进来的
if (tn != null) { // lagging tail
// CAS 把 tail 指向最新的尾巴 tn 节点,
advanceTail(t, tn);
continue;
}
// 已经 timeout 超时了
if (timed && nanos <= 0) // can't wait
return null;
// s 第一次 for 进来就是 null,那么此时包装成节点Node
if (s == null)
s = new QNode(e, isData);
// 设置 t.next 是新进来的这个 Node
if (!t.casNext(null, s)) // failed to link in
continue; // CAS 失败就 continue,这个地方不加lock 和 Synchronized,利用了 CAS 其实挺省性能的
// ===== 前面的全部都是校验检查工作 =====
// === 下面的才是对新进来节点的处理 ===
// 将新进来的 NodeS 插入到队列的尾巴里面去
advanceTail(t, s); // swing tail and wait
// 一般来说,在这里会进入阻塞
Object x = awaitFulfill(s, e, timed, nanos);
// 如果由于超时timeout等情况被取消,那么会进入下面的 if
if (x == s) { // wait was cancelled
clean(t, s);
return null;
}
//匹配的操作到来,s操作完成,离开队列,如果没离开,使其离开(advanceHead 方法会使得 h.next = h 使得 s.isOffList() 为 true)
// 判断s是否被摘掉了 (s.next =this 说明被摘掉了,也就是匹配完毕了)
if (!s.isOffList()) { // not already unlinked
// 推进head ,cas将head 由t(是t不是h),设置为s
// head 设置成 s, 摘掉 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
//不同的模式那么是匹配的(put 匹配take ,take匹配put),出队操作
//队头元素节点
QNode m = h.next; // node to fulfill
//队列发生变化,重来
if (t != tail || m == null || h != head)
continue; // inconsistent read
Object x = m.item;
// 1. 如果isData == (x != null) 为true 那么表示是相同的操作。
//(x!= null为true说明队列第一个元素是 put 操作, isData=true 说明是 put 操作。 x!= null 为 false 说明第一个元素是 take 操作,isData=false说明是 take 操作 )
//其实能进入到上面的if,然后在这个地方出现了这种情况,原因就是多线程条件下,这个线程慢了,导致队列中状态都变了
// 2. x == m 则是被取消了的操作
//m.casItem(x, e) 将 m 的 item 设置为本次操作的 e,实际上这是最重要的匹配操作,如果失败,那么推进 head ,重试
if (isData == (x != null) || // m already fulfilled
x == m || // m cancelled
!m.casItem(x, e)) { // lost CAS
//推进head
advanceHead(h, m); // dequeue and retry
continue;
}
//m.casItem 成功后,也要推进head
advanceHead(h, m); // successfully fulfilled
//唤醒匹配操作的线程
LockSupport.unpark(m.waiter);
return (x != null) ? (E)x : e;
}
}
}
-
advanceTail(t, tn); 这个是TransferQueue 区别与 TransferStack的一个根据,这个是从尾巴插入,从头部取,所以是先进先出。而 TransferStack 是从头部插入,头部读取。后进先出。
-
最重要的细节是:
- m.casItem(x, e) 将 m 的 item 设置为本次操作的 e,实际上这是最重要的匹配操作
- advanceHead(h, m); 节点 s (s并没有进队列)与队列 queue 内的第一个head元素 m 匹配,然后推进 head。
- LockSupport.unpark(m.waiter); 节点 s 与元素 m 匹配后,唤醒 m 节点
- 被唤醒的Node 执行 s.isOffList() 方法返回的是 true。
5.3 casItem 方法 (重要)
- 匹配操作,牵手走人。
- 上方调用 m.casItem(x, e) ,将 m 的 item 设置为本次操作的 e,
boolean casItem(Object cmp, Object val) {
return item == cmp &&
UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
}
public final native boolean compareAndSwapObject(Object o, long offset,
Object expected,
Object x);
5.4 clean 方法
- java.util.concurrent.SynchronousQueue.TransferQueue#clean 方法
- 查看上层调用:
- 流程图:
- 我们注意到在TransferQueue的定义中,除了定义指向哨兵队头的head,队尾的tail,还有一个属性叫cleanMe,该属性在clean方法里面会被用到,主要作用是用来辅助进行清理。
- 当TransferQueue的最后一个元素需要被移除(可能是cancel等原因导致的移除),此时该元素代表队尾,移除动作无法直接在队尾上面执行!
因为TransferQueue是单链表的形式实现,总不可能从头遍历来进行移除,一是效率,二是还要考虑超级多的并发,所以遍历过程可能会因为多线程并发操作导致非一致性的读。 - 所以这里使用了一个叫cleanMe的内部属性来暂存此时需要被移除的队尾节点的前继节点,等待下一次进入该方法的时候进行移除操作(下一次进来tail肯定发生了变化)
/**
* Gets rid of cancelled node s with original predecessor pred.
* 对 中断的 或 等待超时的 节点进行清除操作
*/
void clean(QNode pred, QNode s) {
// 1. 清除掉 thread 引用
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.
*
* 在程序运行中的任何时刻, 最后插入的节点不能被删除(这里的删除指 通过 cas 直接删除, 因为这样直接删除会有多删除其他节点的风险)
* 当 节点 s 是最后一个节点时, 将 s.pred 保存为 cleamMe 节点, 下次再进行清除操作
*/
// 2. 判断 pred.next == s, 下面的 步骤2 可能导致 pred.next = next
while (pred.next == s) { // Return early if already unlinked
QNode h = head;
QNode hn = h.next; // Absorb cancelled first node as head
// 3. hn 中断或者超时, 则推进 head 指针, 若这时 h 是 pred 则 loop 中的条件 "pred.next == s" 不满足, 退出 loop
if (hn != null && hn.isCancelled()) {
advanceHead(h, hn);
continue;
}
QNode t = tail; // Ensure consistent read for tail
// 4. 队列为空, 说明其他的线程进行操作, 删除了节点(注意这里永远会有个 dummy node)
if (t == h)
return;
QNode tn = t.next;
// 5. 其他的线程改变了 tail, continue 重新来
if (t != tail)
continue;
if (tn != null) {
// 6. 帮助推进 tail
advanceTail(t, tn);
continue;
}
// 7. 节点 s 不是尾节点, 则 直接 CAS 删除节点(在队列中间进行这种删除是没有风险的)
if (s != t) { // If not tail, try to unsplice
QNode sn = s.next;
if (sn == s || pred.casNext(s, sn)) // 退出循环
return;
}
// 8. s 是队列的尾节点, 则 cleanMe 出场
QNode dp = cleanMe;
if (dp != null) { // Try unlinking previous cancelled node
// 9. cleanMe 不为 null, 进行删除删一次的 s节点, 也就是这里的节点d
QNode d = dp.next;
QNode dn;
// 10. 这里有几个特殊情况:
// 1. 原来的s节点()也就是这里的节点d已经删除;
// 2. 原来的节点 cleanMe 已经通过 advanceHead 进行删除;
// 3 原来的节点 s已经删除 (所以 !d.siCancelled), 存在这三种情况, 直接将 cleanMe 清除
if (d == null || // d is gone or
d == dp || // d is off list or
!d.isCancelled() || // d not cancelled or
// 11. d 不是tail节点, 且dn没有offlist, 直接通过 cas 删除 上次的节点 s (也就是这里的节点d); 其实就是根据 cleanMe 来清除队列中间的节点
(d != t && // d not tail and
(dn = d.next) != null && // has successor
dn != d && // that is on list
dp.casNext(d, dn))) // d unspliced
// 12. 清除 cleanMe 节点, 这里的 dp == pred 若成立, 说明清除节点s, 成功, 直接 return, 不然的话要再次 loop, 接着到 步骤 13, 设置这次的 cleanMe 然后再返回
casCleanMe(dp, null);
if (dp == pred)
return; // s is already saved node
// 原来的 cleanMe 是 null, 则将 pred 标记为 cleamMe 为下次 清除 s 节点做标识
} else if (casCleanMe(null, pred))
return; // Postpone cleaning s
}
}
- 调用这个方法都是由节点线程Interrupted 或等待 timeout 时调用的, 清除时分两种情况讨论:
- 删除的节点不是queue尾节点, 这时 直接 pred.casNext(s, s.next) 方式来进行删除(和ConcurrentLikedQueue中差不多)
- 删除的节点是队尾节点
- 此时 cleanMe == null, 则前继节点pred标记为 cleanMe, 为下次删除做准备
- 此时 cleanMe != null, 先删除上次需要删除的节点, 然后将 cleanMe至null, 让后再将 pred 赋值给 cleanMe。
这时我们想起了 ConcurrentSkipListMap 中的 marker 节点, 对, marker 和 cleanMe 都是起着防止并发环境中多删除节点的功能
5.5 casCleanMe 方法
boolean casCleanMe(QNode cmp, QNode val) {
return cleanMe == cmp &&
UNSAFE.compareAndSwapObject(this, cleanMeOffset, cmp, val);
}
六、总结
- SynchronousQueue作为一个无数据缓冲的阻塞队列,其内部通过两个内部类(队列和栈)分别实现了公平策略和非公平策略下的队列操作,其实我们需要记住的在于其操作必须是成双成对的,在无超时无中断的情况下,一个线程执行入队操作,必然需要另一个线程执行出队操作,此时两操作互相匹配,同时完成操作,这也是其取名为Synchronous(同时发生)的含义
- 消费者的消费速度与生产者的生产速度不一致,那么如果是采用非公平 TransferStack,那么栈底部的Node可能会超时或者永远不会被读取到。