介绍
SynchronousQueue 是一个无空间的队列(公平模式)或者栈(非公平模式)
任何一个对 SynchronousQueue 读的操作,必须等到一个对 SynchronousQueue 写的操作
任何一个对 SynchronousQueue 写的操作,必须等到一个对 SynchronousQueue 读的操作
对于 SynchronousQueue 而言,内部读写操作都被抽象成 Transferer 来实现,其子类有 TransferQueue 与 TransferStack 两种类型
SynchronousQueue 在没有匹配操作情况下,相同操作过多时,线程会经过一段自旋之后,进入阻塞状态
TransferStack 表示LIFO,后进先出的原则,当有匹配操作时,优先分配给最后一个线程进行匹配
TransferQueue 表示FIFO,先进先出的原则,当有匹配操作时,优先匹配最先进入等待的线程
公平模式是如果有匹配项到来时,会优先分配给前面的线程,在等待匹配项的线程自旋过程中如果仍有匹配项与其匹配,其会结束自旋,执行匹配操作,否则就会进入等待状态
非公平模式是如果有匹配项到来时,会优先匹配最后等待匹配操作的线程,由于最后等待匹配的线程是最后进来的,还可能处于自旋状态,,节省操作时间,效率较高
因此 SynchronousQueue 默认使用 TransferStack 模式,效率较高。类似于加锁操作默认使用非公平锁。
SynchronousQueue 操作流程
流程分析
- 当队列是空或者操作类型与队列末尾操作类型一致,在队列中添加元素,等待匹配操作
- 队列初始化时,会初始化一个空元素,进行匹配操作时,匹配头节点的下一个元素,当匹配完成之后,头节点后移。
- 阻塞线程被唤醒之后,仍然再执行一次匹配流程(不确定是阻塞超时被唤醒,还是正常唤醒)
SynchronousQueue 分析
SynchronousQueue 系统变量
| 字段名称 | 关键修饰 | 说明 |
|---|---|---|
| NCPUS | 当前系统可用CPU核数 | |
| maxTimedSpins | 带有超时时间的最大自旋次数,NCPUS <= 2时,该值为0;NCPUS >=2时,该值为32 只有一个可用CPU核数,自旋毛线啊 | |
| maxUntimedSpins | maxTimedSpins * 16 ,不带有超时时间最大自旋次数,因为其自旋更快 | |
| spinForTimeoutThreshold | 获取匹配操作时,当自旋之后剩余时间 > 该值,将进行带有时间的阻塞等待 | |
| transferer | volatile | 使用公平模式或者非公平模式,默认非公平 |
TransferQueue 系统变量
| 字段名称 | 关键修饰 | 说明 |
|---|---|---|
| head | volatile | 链表头节点,是无效元素,队列初始化时默认创建。节点变更其他线程需要感知 |
| tail | volatile | 链表末尾节点,队列初始化时默认创建。节点变更其他线程需要感知 |
| cleanMe | volatile | 记录取消节点的前驱节点,初次有节点取消,记录前驱节点,第二次有元素取消时,会根据记录的前驱节点执行清理操作。节点变更其他线程需要感知 |
TransferQueue 方法说明
| 方法名称 | 作用 |
|---|---|
| advanceHead | 使用CAS重新设置链表头节点,原头节点next指向自身 |
| advanceTail | 使用CAS重新设置链表尾部节点 |
| casCleanMe | 使用CAS重新设置 cleanMe 节点 |
| transfer | take or put 操作 |
| awaitFulfill | 自旋 阻塞等待匹配操作 |
| clean | 清除无效节点 |
TransferQueue.QNode 属性说明
| 字段名称 | 关键修饰 | 说明 |
|---|---|---|
| next | volatile | 队列等待节点的下一个节点 ,当节点被移出链表,其值为自身。使用volatile 是因为其他线程会有判断 |
| item | volatile | 等待节点的元素值,take为null.当节点匹配操作之后,该值会被替换为匹配操作的元素值。当线程操作被取消时,其值为自身 。使用volatile 是因为其他线程会有判断 |
| waiter | volatile | 当节点自旋次数达到上限之后,还没有匹配到对应的操作,会设置等待线程,等到有操作匹配到该节点之后,通过该属性唤醒指定线程 |
| isData | 表示该节点是否有值 |
TransferQueue.QNode 方法说明
| 方法名称 | 作用 |
|---|---|
| casNext | CAS设置下一个节点,当节点移除链表时其指向自身 |
| casItem | CAS设置元素属性 ,当线程被中断时,其指向自身 |
| tryCancel | 节点取消,设置item指向自身 |
| isCancelled | 判断节点是否被取消,即其item是否等于自身 |
| isOffList | 判断节点是否已经离队,即next是否等于自身 |
TransferQueue.transfer 流程 添加等待队列/匹配节点流程
案例分析按照执行顺序进行,不进行多线程分析 可用CPU核数为4
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;
// 由于 TransferQueue 初始化的时候,对 head,tail 节点已经进行初始化
//如果不存在,说明 TransferQueue 没有构造完成,重入循环等待构造完成
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;
// 先前读取的末尾元素和此时的末尾元素一致,但是此时末尾元素有后继节点,且不为空,那么就将其后继节点作为末尾元素,重新进入循环。
// 当另外线程有新节点创建,已经添加为末尾元素的后继节点,但是还没有设置为末尾节点时出现此case。如果其后继节点还有后继节点,则在下一次循环中再次进行设置
if (tn != null) { // lagging tail
advanceTail(t, tn);
continue;
}
//表示如果需要进行超时管控,但是超时时间 <=0 时,直接返回null
if (timed && nanos <= 0) // can't wait
return null;
// 要操作的元素节点为null时新增元素
// 当通过CAS设置当前操作节点为当前末尾元素的后继节点失败后,重入循环时,s不为空
if (s == null)
s = new QNode(e, isData);
// CAS设置当前操作的节点为先前读取的末尾元素后继节点
if (!t.casNext(null, s)) // failed to link in
continue;
//CAS设置当前操作节点为末尾节点,即使CAS设置失败了,也无影响,因为经过 advanceTail(t, tn); 保证元素一定在队列中
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;
}
//判断节点是否已经离队,节点是从表头清除,清除时会设置其next属性为其自身
if (!s.isOffList()) { // not already unlinked
//如果该节点是表头后的第一个后继节点,那么将该节点设置为表头(表头都是无效的节点)
advanceHead(t, s); // unlink if head
// 如果其有item属性,并且不为空,设置其item = 自身,表明该节点被取消了
if (x != null) // and forget fields
s.item = s;
//设置线程等待为null,释放线程
s.waiter = null;
}
//返回操作结果
return (x != null) ? (E)x : e;
} else { //执行匹配模式 // complementary-mode
// 由于队列头节点都是无效节点,因此从头节点的下一个节点进行匹配操作
// 已经操作离队的节点,其item = 自身
QNode m = h.next; // node to fulfill
// t != tail || h != head 说明链表发生变更,重新进行匹配
// 如果 h != head,则表头第一个后继节点可能不是m,甚至m可能已经完成匹配操作,因此需要重入循环,重新进行查找头节点的第一个后继节点
// m == null 说明队列中已经没有元素了,没有匹配节点,重新进行操作
if (t != tail || m == null || h != head)
continue; // inconsistent read
// 获取头元素后继节点的属性信息
Object x = m.item;
// 当 isData 为true时,说明当前操作是put操作,其匹配操作为take,对应的item = null,如果此时 item不为null时,说明节点已经被取消或者已经完成匹配操作
// 同理当 isData 为false时,说明当前操作是take操作,其匹配操作为put,对应的item != null,如果此时 item == null时,说明节点已经被取消或者已经完成匹配操作
if (isData == (x != null) || // m already fulfilled
// 如果take操作时,其匹配的节点item不为空,但是为其自身时,说明其匹配节点已经被取消操作了
x == m || // m cancelled
// 对匹配节点CAS设置对应的item值,如果设置失败,说明有节点已经对其进行了设置,即进行了匹配操作
!m.casItem(x, e)) { // lost CAS
// 头指针后移,将匹配节点离队
advanceHead(h, m); // dequeue and retry
continue;
}
// 已经匹配成功,将匹配节点离队
advanceHead(h, m); // successfully fulfilled
// 将匹配节点的等待线程唤醒
LockSupport.unpark(m.waiter);
return (x != null) ? (E)x : e;
}
}
}
TransferQueue.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();
// 如果当前操作节点是第一个有效的节点,进行自旋匹配
// 否则会执行两次匹配操作,如果没有匹配到会进入阻塞状态
// 第一次是初入循环,没有匹配到相应操作,设置 waiter
// 第二次是设置完 waiter 之后,重入循环,没有匹配到直接进入阻塞
int spins = ((head.next == s) ?
(timed ? maxTimedSpins : maxUntimedSpins) : 0);
for (;;) {
//线程中断,线程外主动中断,线程阻塞超时中断
if (w.isInterrupted())
//当前操作节点操作取消,设置其 item为自身
s.tryCancel(e);
Object x = s.item;
// s.item 初始值为 e
// 当操作取消时,item为其自身
// 当匹配操作时,item 为其匹配操作的值
if (x != e)
return x;
if (timed) {
//剩余时间
nanos = deadline - System.nanoTime();
//如果剩余时间小于0,将操作取消
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);
//如果剩余可用时间 > 1000ns,直接进入阻塞状态,小于1s的话,剩余时间做自旋处理,不阻塞
// 如果时间 > 1000 ns还进行自旋,可能会浪费性能,短时间内使用自旋
else if (nanos > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanos);
}
}
TransferQueue.clean 流程 节点取消流程
void clean(QNode pred, QNode s) {
// 首先将节点的等待线程置为null,释放线程
s.waiter = null; // forget thread
// 判断前驱节点的后继节点是否是当前节点
// 当前节点变为头节点时,前驱节点的后继节点被设置为前驱节点自身,退出循环
while (pred.next == s) { // Return early if already unlinked
QNode h = head;
QNode hn = h.next; // Absorb cancelled first node as head
// 移动头指针位置,剔除已经取消的节点
// 当s节点为头节点后的首节点,在这里进行清理
// 当只有一个有效节点且删除时,尾部节点时不动的,头节点指针向后移动,也指向该节点,达到首尾指针指向同一个节点的效果,此时队列是空的
if (hn != null && hn.isCancelled()) {
// 头指针后移,旧头节点 next指向自身
advanceHead(h, hn);
continue;
}
QNode t = tail; // Ensure consistent read for tail
// t == h 说明队列是空的
if (t == h)
return;
QNode tn = t.next;
// 进入循环时刻与此时末尾元素不一致,说明有新的节点加入
// 为什么要重新进入循环?
if (t != tail)
continue;
// 说明此时末尾节点还有后继节点,尝试重新设置尾部节点
if (tn != null) {
// 拿进入循环时旧的末尾节点作为设置时的比较值,如果此时末尾元素不是 t ,就不会执行CAS设置末尾节点
advanceTail(t, tn);
continue;
}
//首次有节点删除,会设置cleanMe节点
//第二次有节点删除,会删除cleanMe后继节点,重新设置cleanMe
// 说明操作的节点不是末尾节点
// 取消节点是中间节点,在这里进行清理
if (s != t) { // If not tail, try to unsplice
QNode sn = s.next;
// 如果 sn == s 说明 s节点的后继节点与s一直,节点已经离队,不需要再处理
// pred.casNext(s, sn) 设置 s 的前驱节点的后继节点为s的后继节点,如果没有设置成功,说明已经前驱节点的后置节点已经发生变更
if (sn == s || pred.casNext(s, sn))
return;
}
//删除的节点是尾部节点
//记录要清理节点的前驱节点,最开始时为null
// deletePrevious
QNode dp = cleanMe;
if (dp != null) { // Try unlinking previous cancelled node
//获取要取消的节点
//delete
QNode d = dp.next;
//deleteNext
QNode dn;
//删除节点为null
if (d == null || // d is gone or
// 要删除的节点已经离队
d == dp || // d is off list or
// 要被清除的节点是有效状态(什么时候其是有效状态?)可能有线程已经进行清理了,d是一个新的等待节点
!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
}
}
节点匹配流程
public static void main(String[] args) throws InterruptedException {
SynchronousQueue<Integer> queue = new SynchronousQueue<>(true);
Thread thread1 = new Thread(() -> {
try {
queue.put(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread1.start();
Thread.sleep(200);
Thread thread2 = new Thread(() -> {
try {
queue.put(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread2.start();
Thread.sleep(200);
Thread thread3 = new Thread(() -> {
try {
queue.put(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread3.start();
Thread.sleep(200);
Thread thread4 = new Thread(() -> {
try {
queue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread4.start();
}
节点信息初始化
TransferQueue() {
QNode h = new QNode(null, false); // initialize to dummy node.
head = h;
tail = h;
}
第一个put操作
Thread thread1 = new Thread(() -> {
try {
queue.put(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread1.start();
执行put操作会发生什么呢? 看一下操作流程
QNode s = null; // constructed/reused as needed
//获取当前操作是否有属性值
boolean isData = (e != null);
QNode t = tail;
QNode h = head;
// 由于 TransferQueue 初始化的时候,对 head,tail 节点已经进行初始化
//如果不存在,说明 TransferQueue 没有构造完成,重入循环等待构造完成
if (t == null || h == null) // saw uninitialized value
continue; // spin
//当队列中没有等待节点时 首尾节点相同
//当前操作与末尾等待节点操作一致时,当前操作需要入队等待
if (h == t || t.isData == isData) { // empty or same-mode
}
//返回操作结果
return (x != null) ? (E)x : e;
} else {
}
先判断队列是否已经初始化完成
if (t == null || h == null) // saw uninitialized value
continue; // spin
队列已经进行初始化了,初始化完成之后,tail和head都是不为null,如果出现这种case,进行自旋,一直等到队列初始化完毕
再判断队列是否是空的或者当前操作与队列末尾元素操作是否一致
if (h == t || t.isData == isData) { // empty or same-mode
}
此时队列中h,t都指向默认的节点 Ref-371,因此条件判定成功,进入该条件下进行执行
QNode tn = t.next;
// 当并发时,先前读取的末尾元素与现在的末尾元素不一致,重新读取
if (t != tail) // inconsistent read
continue;
// 先前读取的末尾元素和此时的末尾元素一致,但是此时末尾元素有后继节点,且不为空,那么就将其后继节点作为末尾元素,重新进入循环。
// 当另外线程有新节点创建,已经添加为末尾元素的后继节点,但是还没有设置为末尾节点时出现此case。如果其后继节点还有后继节点,则在下一次循环中再次进行设置
if (tn != null) { // lagging tail
advanceTail(t, tn);
continue;
}
//表示如果需要进行超时管控,但是超时时间 <=0 时,直接返回null
if (timed && nanos <= 0) // can't wait
return null;
// 要操作的元素节点为null时新增元素
// 当通过CAS设置当前操作节点为当前末尾元素的后继节点失败后,重入循环时,s不为空
if (s == null)
s = new QNode(e, isData);
// CAS设置当前操作的节点为先前读取的末尾元素后继节点
if (!t.casNext(null, s)) // failed to link in
continue;
//CAS设置当前操作节点为末尾节点,即使CAS设置失败了,也无影响,因为经过 advanceTail(t, tn); 保证元素一定在队列中
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;
}
//判断节点是否已经离队,节点是从表头清除,清除时会设置其next属性为其自身
if (!s.isOffList()) { // not already unlinked
//如果该节点是表头后的第一个后继节点,那么将该节点设置为表头(表头都是无效的节点)
advanceHead(t, s); // unlink if head
// 如果其有item属性,并且不为空,设置其item = 自身,表明该节点被取消了
if (x != null) // and forget fields
s.item = s;
//设置线程等待为null,释放线程
s.waiter = null;
}
//返回操作结果
return (x != null) ? (E)x : e;
由于此时队列中只有一个默认节点,也没有其他线程进行竞争,因此 t == tail成立,tn == null成立
由于没有设置超时时间管控,因此 timed && nanos <= 0 成立
此时 当前节点s是空的,因此创建一个s节点
创建s节点完毕之后,通过CAS设置当前末尾节点的后继节点为s
如果设置后继节点时失败,说明有新的线程执行添加操作,重入循环,再次进行处理 将末尾节点添加后继节点之后,通过CAS将当前节点设置为末尾节点,此时无需关注是否设置成功,如果设置不成功,说明已经当前t已经不是末尾节点,但是由于 s 已经关联为t的后继节点,当有线程操作
QNode tn = t.next;
if (tn != null) {
advanceTail(t, tn);
continue;
}
时,会尝试设置 t 后的每个节点作为末尾节点,将 s 加入到队列中
设置完末尾节点之后,执行 awaitFulfill 匹配操作节点
首先计算需要自旋的次数,如果操作节点不是头节点后的第一个节点,自旋次数为0,进行第一次没有匹配操作设置等待者线程和再次没有对应的匹配操作之后,直接进入等待状态
由于不需要进行时间管控,并且可用CPU核数 >=2 ,自旋次数为512
int spins = ((head.next == s) ? (timed ? maxTimedSpins : maxUntimedSpins) : 0);
由于队列是先进先出的,优先匹配前面的节点,现在 s 节点又不是第一个有效的节点,没有必要进行自旋凑热闹,经过少量几次判断之后,直接让它进入等待状态,自己等着去吧。如果是需要进行超时管控,并且剩余的可以使用的时间 <= 1000ns,则会一直进行自旋
for (;;) {
//线程中断,线程外主动中断,线程阻塞超时中断
if (w.isInterrupted())
//当前操作节点操作取消,设置其 item为自身
s.tryCancel(e);
Object x = s.item;
// s.item 初始值为 e
// 当操作取消时,item为其自身
// 当匹配操作时,item 为其匹配操作的值
if (x != e)
return x;
if (timed) {
//剩余时间
nanos = deadline - System.nanoTime();
//如果剩余时间小于0,将操作取消
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);
//如果剩余可用时间 > 1000ns,直接进入阻塞状态,小于1s的话,剩余时间做自旋处理,不阻塞
// 如果时间 > 1000 ns还进行自旋,可能会浪费性能,短时间内使用自旋
else if (nanos > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanos);
}
首先判断当前线程是否被中断了,如果中断了,则将操作节点进行取消操作,此时线程肯定不处于中断状态,因此该判断不执行
通过item属性判断节点是否已经被其他操作匹配
Object x = s.item;
if (x != e)
return x;
分析可知,此时没有任何其他线程操作,该判断不生效
由于该操作没有超时时间管控,因此 不进行时间管控的判断
if (timed) {
//剩余时间
nanos = deadline - System.nanoTime();
//如果剩余时间小于0,将操作取消
if (nanos <= 0L) {
s.tryCancel(e);
continue;
}
}
此时spins = 512,spins > 0 因此直接执行
--spins;
在自循环512次之后,仍然没有匹配的对应操作,此时当前操作节点等待线程为null,设置节点等到线程为当前线程
s.waiter = w;
设置等待者线程之后状态为
继续下一次循环,没有匹配到对应的操作,没有超时时间管控,进行阻塞等待
if (s.waiter == null)
s.waiter = w;
至此,第一个线程添加元素流程分析完毕
第二个put操作状态
第三个put操作状态
第四个线程进行take操作
boolean isData = (e != null);
take 操作 e 属性为null,isData 判断为false
此时 t = Reference-374 h = Reference-371 t.isData = true 因此判定队列是空队列或者当前操作模式与队列最后一个节点操作模式一致不成立
(h == t || t.isData == isData) = false
因此进入匹配流程
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 already fulfilled
x == m || // m cancelled
!m.casItem(x, e)) { // lost CAS
advanceHead(h, m); // dequeue and retry
continue;
}
advanceHead(h, m); // successfully fulfilled
LockSupport.unpark(m.waiter);
return (x != null) ? (E)x : e;
此时
m = Reference-372
此时无其他线程操作匹配流程,因此 t == tail,h == head
并且此时头节点有后继节点 m != null(如果m==null,说明队列是一个空队列,没有元素可以进行匹配操作,继续重入循化,创建新的节点,添加入等待队列中)
因此继续向下走
执行判断
isData == (x != null) || x == m || !m.casItem(x, e)
我们先来分析一下正常流程,然后看一下这个判断应该怎么解读 我们先来看一下 !m.casItem(x, e) 这个流程 假设 (isData == (x != null) || x == m) 都为false的话,那么 m.casItem(x, e) 成功之后,就可以不执行下列操作
advanceHead(h, m);
continue;
通过看源码分析可得,m.casItem(x, e) 是将 m 节点的item值替换成当前操作传入的元素值,那么 take操作传入的元素值是什么呢?take操作传入的元素值是null,因此操作成功之后,当前状态为
操作成功之后,将头指针head位置后移,并将原来头指针h的next指向自身,因此执行完
advanceHead(h, m);
之后状态为
然后执行
LockSupport.unpark(m.waiter);
唤醒等待线程
最后返回值信息
好了,通过上述正常流程我们了解到了: 就是将匹配节点的属性值替换成当前操作的属性值,那么我们再分析一下上面的判断逻辑
isData == (x != null) || x == m || !m.casItem(x, e)
首先分析一下 x 是匹配节点的属性值 = 1
isData == (x != null)
如果当前操作是take操作,那么 isData = false, 匹配put节点的属性值不为空。如果该判断成功说明了什么?说明了 x == null。本来m节点的item有值,现在没有值了。如果当前操作为put,那么isData=true,匹配take节点属性值为 null,如果判断成立,说明 x == null。
这说明了什么?说明了有人捷足先登,先和这个节点进行了匹配,而且还匹配成功了。就像是要结婚的时候,最后进行一次检验时,发现对方已经名花有主了,这TM还玩毛线啊,重入循环进行重新配对去吧,如果没有等待配对的,那就劳烦您去等着被别人配对去吧。
然后分析
x == m
呃...... 这个还没有讲到,现在分析了也是白分析,记住下,当有节点的线程被中断的时候,会将该节点的item属性替换成其本身,当 节点 == 节点.item 时,说明该节点已经被取消了。就像结婚的时候,结果新娘子失踪了,挂了,跑了。。。反正怎么滴吧,就是这个婚结不成了,重新找一个去吧。
最后分析
!m.casItem(x, e)
这个表示通过CAS方式设置匹配元素的 item值,如果设置失败,就代表者和结婚对象去领证的时候,发现我们准备领证的时候,他/她的另一半还没有人的,到了领证环节,TMD发现已经有另一半了,这还领什么证啊,重婚罪了。回去换一个吧
第一个线程被唤醒,继续后续操作
好了,我们把思路拉回来,当take操作有匹配节点的时候,完成匹配操作,然后唤醒匹配线程,就这样 节点Reference-372 的等待线程 thread-0 被唤醒了,然后继续执行
for (;;) {
//线程中断,线程外主动中断,线程阻塞超时中断
if (w.isInterrupted())
//当前操作节点操作取消,设置其 item为自身
s.tryCancel(e);
Object x = s.item;
// s.item 初始值为 e
// 当操作取消时,item为其自身
// 当匹配操作时,item 为其匹配操作的值
if (x != e)
return x;
if (timed) {
//剩余时间
nanos = deadline - System.nanoTime();
//如果剩余时间小于0,将操作取消
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);
}
此时 Reference-372 所处的系统状态为
重入循环时,当前 e = 1, x=Reference-372.item = null, 因此判断
(x != e )
成真,返回匹配的值 为 null
返回主流程
返回到主流程中继续向下流转
// 自旋等待匹配/线程中断结果,达到自旋次数没有匹配结果,当先线程会阻塞或者取消操作
Object x = awaitFulfill(s, e, timed, nanos);
// 当线程被中断或者已经到达超时时间,会取消操作,返回结果为其自身
if (x == s) { // wait was cancelled
//清除被取消的节点(惰性清除)
clean(t, s);
return null;
}
//判断节点是否已经离队,节点是从表头清除,清除时会设置其next属性为其自身
if (!s.isOffList()) { // not already unlinked
//如果该节点是表头后的第一个后继节点,那么将该节点设置为表头(表头都是无效的节点)
advanceHead(t, s); // unlink if head
// 如果其有item属性,并且不为空,设置其item = 自身,表明该节点被取消了
if (x != null) // and forget fields
s.item = s;
//设置线程等待为null,释放线程
s.waiter = null;
}
//返回操作结果
return (x != null) ? (E)x : e;
由上面分析可得
Object x = awaitFulfill(s, e, timed, nanos);
x = null
因此判断 x == s 不成立,继续向下流转 此时 s.next = Reference-373
boolean isOffList() {
return next == this;
}
对于当前操作s节点而言,其next属性不是其自身,因此判断 !s.isOffList() 为真 执行
advanceHead(t, s);
void advanceHead(QNode h, QNode nh) {
if (h == head &&
UNSAFE.compareAndSwapObject(this, headOffset, h, nh))
h.next = h; // forget old next
}
由于 此时t不是表头,因此设置 advanceHead(t, s); 失败,不过不影响后续流程的进行
此种case适用于当前操作节点在队列最末尾时候,前面的节点都已经匹配完毕,当前节点升级为头部节点
那么为什么执行 针对 x != null 执行 s.item = s 操作,而不是所有都执行呢?执行这个有什么含义呢?
if (x != null)
s.item = s;
是将匹配的元素值释放
个人理解该代码块执行逻辑需要与匹配操作结合理解
if (isData == (x != null) || // m already fulfilled
x == m || // m cancelled
!m.casItem(x, e)) { // lost CAS
advanceHead(h, m); // dequeue and retry
continue;
}
既然 x != null ,说明当前操作是take操作,匹配完了结果就可以释放了,那么我直接赋值为 s.item = null不就可以了吗?怎么还要赋值 s.item = s ? 如果赋值为null,此时节点还在队列中,那么可能有别put操作进行匹配到该节点,然后进行 (isData == (x != null) || x == m 的话该判断都为false,那么就会执行m.casItem(x, e),就会造成已经被匹配完成的节点又一次被匹配了。又TMD重婚了。因此将其 s.item 设置为 s,表示这个节点已经被匹配了,是无效的了,碰到了就赶紧脚底抹油,跑吧。
那么为什么 x == null的时候,不用进行赋值呢? x==null,说明当前操作是put操作,等待take操作,take操作再一次进行匹配时 isData == (x != null) 就成立了,表示该匹配的节点已经有人捷足先登了,拜拜吧您嘞!
接着继续就将节点的等待线程设置为null 最后将结果返回即可
至此,正常的匹配流程分析完毕!!!
队列第一个节点取消
public static void main(String[] args) throws InterruptedException {
SynchronousQueue<Integer> queue = new SynchronousQueue<>(true);
Thread thread1 = new Thread(() -> {
try {
queue.put(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread1.start();
Thread.sleep(200);
Thread thread2 = new Thread(() -> {
try {
queue.put(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread2.start();
Thread.sleep(200);
Thread thread3 = new Thread(() -> {
try {
queue.put(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread3.start();
Thread.sleep(200);
thread1.interrupt();
在执行 thread1.interrupt(); 之前,三个线程的节点都加入到队列中。 当 thread1 线程中断时,重入循环
for (;;) {
//线程中断,线程外主动中断,线程阻塞超时中断
if (w.isInterrupted())
//当前操作节点操作取消,设置其 item为自身
s.tryCancel(e);
Object x = s.item;
// s.item 初始值为 e
// 当操作取消时,item为其自身
// 当匹配操作时,item 为其匹配操作的值
if (x != e)
return x;
判断 w.isInterrupted() = true,因为 thread1 线程确实被中断了,然后执行 s.tryCancel(e) 操作
UNSAFE.compareAndSwapObject(this, itemOffset, cmp, this);
通过CAS设置其自身item = 自身节点,表示节点取消了
此时系统中的节点状态为
然后执行 Object x = s.item; 得到 x = s; 判断
x != e = true
返回值 为 s,因此当线程中断时, 主流程执行
Object x = awaitFulfill(s, e, timed, nanos);
得到 x = s; 继续执行主流程
if (x == s) { // wait was cancelled
//清除被取消的节点(惰性清除)
clean(t, s);
return null;
}
其中 x == s 判断成真,执行 clean(t, s);操作
传入的 pred 代表t,传入的s代表就是s
/**
void clean(QNode pred, QNode s) {
s.waiter = null; // forget thread
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()) {
// head 指针后移,原头元素.next指向自身
// 如果s是头元素的下一个元素 pred 是头元素,那么当其被取消后,那么 s 变为头元素,原头元素的next指向自身
advanceHead(h, hn);
continue;
}
QNode t = tail; // Ensure consistent read for tail
// 集合是空的,直接退出
if (t == h)
return;
QNode tn = t.next;
// 确保t是末尾元素
if (t != tail)
continue;
// 如果末尾元素有next节点,且不是末尾元素,设置 t.next 为末尾元素
if (tn != null) {
advanceTail(t, tn);
continue;
}
// s 不是末尾元素
if (s != t) { // If not tail, try to unsplice
QNode sn = s.next;
// TODO 什么时候 s.next == s ? 当节点从链表中移除时 s.next == s ,
// pred.casNext(s, sn) 设置 s.next 作为 s 前一个元素的后继节点
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 && // 不是末尾元素
// 被清除的节点有后继节点
(dn = d.next) != null && // has successor
// 如果一个节点其下一个节点指向其本身,那么这个节点是已经被无效的节点
dn != d && // that is on list
// 设置被清除前一个节点的后继节点是 清除元素的后继节点
dp.casNext(d, dn))) // d unspliced
// 清空cleanMe节点
casCleanMe(dp, null);
if (dp == pred)
return; // s is already saved node
} else if (casCleanMe(null, pred))
return; // Postpone cleaning s
}
}
首先执行 s.waiter = null 将当前节点等待线程状态设置为null,现在节点状态为
然后判断
(pred.next == s ) == true
进入循环中了,再获取 hn = head.next 就是s节点,因为它是头节点后的第一个节点 判断 s 节点不为空,执行判断 hn.isCancelled()
boolean isCancelled() {
return item == this;
}
由于s节点已经在 awaitFulfill 中被取消了,设置其item = this,因此 isCancelled 为真,故而执行
advanceHead(h, hn);
将头指针后移,并将 h.next设置为h,此时系统状态为
好了,执行这个操作之后,相当于是把原来的头节点剔除了,当前要取消的节点作为新的头节点,反正头节点都是无效的,因此也就相当于将当前要删除的节点无效了
然后重新进入循环
注意,此时 pred.next 已经不是 s 了,变成了 pred 本身了
因此 执行 pred.next == s 结果为false,因此退出循环
故而 clean 方法执行完毕,回到主流程,返回给调用者 null信息
这一块的作用就是将头节点后面状态时取消的节点都进行清除
while (pred.next == s) {
QNode h = head;
QNode hn = h.next; // Absorb cancelled first node as head
if (hn != null && hn.isCancelled()) {
advanceHead(h, hn);
continue;
}
}
取消队列中中间节点
public static void main(String[] args) throws InterruptedException {
SynchronousQueue<Integer> queue = new SynchronousQueue<>(true);
Thread thread1 = new Thread(() -> {
try {
queue.put(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread1.start();
Thread.sleep(200);
Thread thread2 = new Thread(() -> {
try {
queue.put(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread2.start();
Thread.sleep(200);
Thread thread3 = new Thread(() -> {
try {
queue.put(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread3.start();
Thread.sleep(200);
thread2.interrupt();
当三个线程都已经加入到队列中,在第二个线程中断时,进入clean 操作时,其状态为
执行 s.waiter = null 后其状态为
判断 pred.next == s 为true,进入到循环内部
由于 hn != null 且 hn 节点没有取消,不进入判断内部执行,继续向下流转
其中 t = tail != h 表示集合集合不是空的,且 tn == null
因此执行判断 s != t 表明当前节点不是末尾节点
此时系统状态为
此时 sn != s 执行 通过CAS设置 pred.next = sn,设置成功之后,状态变为 sn == s 表示当前节点已经被移出队列了,可能有其他线程操作的,就无需再操作了。
如果通过CAS设置失败,将前驱节点进行记录,延后进行处理
删除末尾元素
public static void main(String[] args) throws InterruptedException {
SynchronousQueue<Integer> queue = new SynchronousQueue<>(true);
Thread thread1 = new Thread(() -> {
try {
queue.put(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread1.start();
Thread.sleep(200);
Thread thread2 = new Thread(() -> {
try {
queue.put(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread2.start();
Thread.sleep(200);
Thread thread3 = new Thread(() -> {
try {
queue.put(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread3.start();
Thread.sleep(200);
thread3.interrupt();
Thread.sleep(200);
Thread thread4 = new Thread(() -> {
try {
queue.put(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread4.start();
Thread.sleep(200);
thread4.interrupt();
Thread.sleep(200);
}
将线程3中断时,进入 clean 操作状态为
首先将等待线程设置为null, 并且表头的后继节点不为 null,并且节点没有取消
而且要取消的节点是队列中最后一个节点,判断 tn != null 不成立,因此执行
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 && // 不是末尾元素
// 被清除的节点有后继节点
(dn = d.next) != null && // has successor
// 如果一个节点其下一个节点指向其本身,那么这个节点是已经被无效的节点
dn != d && // that is on list
// 设置被清除前一个节点的后继节点是 清除元素的后继节点
dp.casNext(d, dn))) // d unspliced
// 清空cleanMe节点
casCleanMe(dp, null);
if (dp == pred)
return; // s is already saved node
} else if (casCleanMe(null, pred))
return; // Postpone cleaning s
此时队列中状态为
此时 cleanMe没有进行过赋值操作,是null的,因此执行
casCleanMe(null, pred)
此时链表状态为
然后现在线程4进行put操作,添加一个节点进入链表,之后又进行了取消操作,线程4进入clean方法后,进行等待线程置空等操作后,其状态为
由于此时 cleanMe节点不为空,因此执行
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 && // 不是末尾元素
// 被清除的节点有后继节点
(dn = d.next) != null && // has successor
// 如果一个节点其下一个节点指向其本身,那么这个节点是已经被无效的节点
dn != d && // that is on list
// 设置被清除前一个节点的后继节点是 清除元素的后继节点
dp.casNext(d, dn))) // d unspliced
// 清空cleanMe节点
casCleanMe(dp, null);
if (dp == pred)
return; // s is already saved node
我们先将用到的节点标注在状态图上看一下
我们来看一下这个恶心的,又臭又长的判断是什么意思
d == null ||
d == dp ||
!d.isCancelled() ||
(d != t && (dn = d.next) != null && dn != d && dp.casNext(d, dn))
- 先看一下 d == null,在cleanMe没有信息的时候,对cleanMe进行赋值,其赋值的结果是要删除节点的前驱节点,那么cleanMe.next就是要删除的节点d, 如果节点d不存在,说明要删除的节点已经不在队列中了,就不需要再进行删除节点操作,cleanMe即可,此 case 为 false
- d == dp 说明 dp本身就是一个已经离队的节点(只有当头节点出队的时候,才会设置其 next值为其自身)。由于删除节点已经出队列了,那么其后继要删除的节点最不济也是新的头节点,众所周知,头节点都是无效的节点,因此无需再进行节点删除操作 ,cleanMe即可 。此case为false
- !d.isCancelled() 表示节点不是取消状态,那么可能有另外的线程进行操作了取消,导致其后继节点是有效的节点.此case为false
由于前三个判断都为false,继续执行第4个判断
4. (d != t && (dn = d.next) != null && dn != d && dp.casNext(d, dn))
d != t 表示要删除的元素不是末尾元素
(dn = d.next) != null 表示要删除的元素有后继节点
dn != d 表示要删除元素的后继节点与自身不一致,说明要删除的节点还在队列中,没有出队
这种情况下就设置要删除元素的前驱节点dp的后继节点为删除元素的后继节点,这样就将删除的元素清理出队列中
执行成功后的状态为
清理完成之后,释放cleanMe节点
此时 dp != pred,因此重入循环,此时cleanMe已经为空,故而重新设置cleanMe节点