并发条件队列之Condition 精讲 (4)

注意: 我们此时并没有断开node的nextWaiter,所以最后一定不要忘记将这个链接断开。
       再回到transferAfterCancelledWait调用处,可知,由于transferAfterCancelledWait将返回true,现在checkInterruptWhileWaiting将返回THROW_IE,这表示我们在离开await方法时应当要抛出THROW_IE异常。

// .... while (!isOnSyncQueue(node)) { LockSupport.park(this); // 线程挂起的地方 // 线程将在这里被挂起,停止运行 // 能执行到这里说明要么是signal方法被调用了,要么是线程被中断了 // 所以检查下线程被唤醒的原因,如果是因为中断被唤醒,则跳出while循环 if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; } // 线程将在同步队列中利用进行acquireQueued方法进行“阻塞式”争锁, // 抢到锁就返回,抢不到锁就继续被挂起。因此,当await()方法返回时, // 必然是保证了当前线程已经持有了lock锁 // 我们这里假设它获取到了锁了,由于我们这时 // 的interruptMode = THROW_IE,则会跳过if语句。 if (acquireQueued(node, savedState) && interruptMode != THROW_IE) interruptMode = REINTERRUPT; // 遍历链表了,把链表中所有没有在等待的节点都拿出去,所以这里调用 // unlinkCancelledWaiters方法,该方法我们在前面await()第一部分的分析 // 的时候已经讲过了,它就是简单的遍历链表,找到所有waitStatus // 不为CONDITION的节点,并把它们从队列中移除 if (node.nextWaiter != null) // clean up if cancelled unlinkCancelledWaiters(); // 这里我们的interruptMode=THROW_IE,说明发生了中断, // 则将调用reportInterruptAfterWait if (interruptMode != 0) reportInterruptAfterWait(interruptMode); } } // 在interruptMode=THROW_IE时,就是简单的抛出了一个InterruptedException private void reportInterruptAfterWait(int interruptMode) throws InterruptedException { if (interruptMode == THROW_IE) throw new InterruptedException(); else if (interruptMode == REINTERRUPT) selfInterrupt(); }

       interruptMode现在为THROW_IE,则我们将执行break,跳出while循环。接下来我们将执行acquireQueued(node, savedState)进行争锁,注意,这里传入的需要获取锁的重入数量是savedState,即之前释放了多少,这里就需要再次获取多少
情况一总结:

线程因为中断,从挂起的地方被唤醒

随后,我们通过transferAfterCancelledWait确认了线程的waitStatus值为Node.CONDITION,说明并没有signal发生过

然后我们修改线程的waitStatus为0,并通过enq(node)方法将其添加到同步队列中

接下来线程将在同步队列中以阻塞的方式获取,如果获取不到锁,将会被再次挂起

线程在同步队列中获取到锁后,将调用unlinkCancelledWaiters方法将自己从条件队列中移除,该方法还会顺便移除其他取消等待的锁

最后我们通过reportInterruptAfterWait抛出了InterruptedException

因此:

       由此可以看出,一个调用了await方法挂起的线程在被中断后不会立即抛出InterruptedException,而是会被添加到同步队列中去争锁,如果争不到,还是会被挂起;

       只有争到了锁之后,该线程才得以从同步队列和条件队列中移除,最后抛出InterruptedException。

       所以说,一个调用了await方法的线程,即使被中断了,它依旧还是会被阻塞住,直到它获取到锁之后才能返回,并在返回时抛出InterruptedException。中断对它意义更多的是体现在将它从条件队列中移除,加入到同步队列中去争锁,从这个层面上看,中断和signal的效果其实很像,所不同的是,在await()方法返回后,如果是因为中断被唤醒,则await()方法需要抛出InterruptedException异常,表示是它是被非正常唤醒的(正常唤醒是指被signal唤醒)

情况二中断发生时,线程已经被唤醒过包含以下两种情况
a. 被唤醒时,已经发生了中断,但此时线程已经被signal过了

final boolean transferAfterCancelledWait(Node node) { // 线程A执行到这里,CAS操作将会失败 if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) { enq(node); return true; } // 由于中断发生前,线程已经被signal了,则这里只需要等待线程成功进入同步即可 while (!isOnSyncQueue(node)) Thread.yield(); return false; }

       由于transferAfterCancelledWait返回了false,则checkInterruptWhileWaiting方法将返回REINTERRUPT,这说明我们在退出该方法时只需要再次中断因为signal后条件队列加入到了同步队列中所以node.nextWaiter为空了,所以直接走到了reportInterruptAfterWait(interruptMode)方法

if (node.nextWaiter != null) // clean up if cancelled unlinkCancelledWaiters(); // 这里我们的interruptMode=THROW_IE,说明发生了中断, // 则将调用reportInterruptAfterWait if (interruptMode != 0) reportInterruptAfterWait(interruptMode); } } // 在interruptMode=THROW_IE时,就是简单的抛出了一个InterruptedException private void reportInterruptAfterWait(int interruptMode) throws InterruptedException { if (interruptMode == THROW_IE) throw new InterruptedException(); // 这里并没有抛出中断异常,而只是将当前线程再中断一次。 else if (interruptMode == REINTERRUPT) selfInterrupt(); }

情况二中的第一种情况总结:

线程从挂起的地方被唤醒,此时既发生过中断,又发生过signal

随后,我们通过transferAfterCancelledWait确认了线程的waitStatus值已经不为Node.CONDITION,说明signal发生于中断之前

然后,我们通过自旋的方式,等待signal方法执行完成,确保当前节点已经被成功添加到同步队列中

接下来线程将在同步队列中以阻塞的方式获取锁,如果获取不到,将会被再次挂起

最后我们通过reportInterruptAfterWait将当前线程再次中断,但是不会抛出InterruptedException

b. 被唤醒时,并没有发生中断,但是在抢锁的过程中发生了中断

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wssfjf.html