如果取消的是头节点的下一个节点,且不是尾节点的情况时,它是唤醒下一个节点,唤醒之前并没有将其移除队列,而是在唤醒下一个节点的时候,shouldParkAfterFailedAcquire里面将取消的节点移除队列,唤醒之后,当前节点的下一个节点也设置成自己,帮助GC回收它。
如果取消节点是中间的节点,则直接将其前节点的下一个节点设置为取消节点的下下个节点即可。
第一种情况如果我们取消的节点是前节点是头节点,此时线程1的节点应该是被中断操作,此时进入到cancelAcquire之后会进入else语句中,然后进去到unparkSuccessor方法,当进入到这个方法之前我们看一下状态变化:
我们发现线程1的Node节点的waitStatus变为1也就是Node.CANCELLED节点,然后运行unparkSuccessor方法,该方法上面就已经讲述了其中的源码,这里就不在贴源码了,就是要唤醒下一个没有被取消的节点,这里是Ref-695这个线程,当Ref-695被唤醒之后它会继续运行下面的内容:
private void doAcquireInterruptibly(int arg) throws InterruptedException { final Node node = addWaiter(Node.EXCLUSIVE); boolean failed = true; try { for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { //再一次循环发现还是没有争抢到锁 setHead(node); p.next = null; // help GC failed = false; return; } if (shouldParkAfterFailedAcquire(p, node) && //再一次循环之后有运行到这里了 parkAndCheckInterrupt()) //这里被唤醒了,又要进行循环操作了 throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); } }发现再一次循环操作后,还是没有正抢到锁,这时候还是会运行shouldParkAfterFailedAcquire方法,这个方法内部发现前节点的状态是Node.CANCELLED这时候它会在内部先将节点给干掉,也就是这个代码:
if (ws > 0) { /* * Predecessor was cancelled. Skip over predecessors and * indicate retry. */ do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; }最后还是会被挂起状态,因为没有释放锁操作,最后移除的节点如下所示:
如果取消的事尾节点,也就是线程3被中断操作,这个是比较简单的直接将尾节点删除即可,其中会走如下代码:
if (node == tail && compareAndSetTail(node, pred)) { compareAndSetNext(pred, predNext, null); }如果取消的节点是中间的节点,通过上例子中则是取消线程2,其实它内部只是将线程取消线程的前节点的下一个节点指向了取消节点的下节点,如下图所示:
结束语这章节分析的主要是ReentrantLock的内部原理,本来公平模式和非公平模式想放在一起来写,无奈发现篇幅有点长了,所以就分开进行写,这样读取来不会那么费劲,内部还有条件内容等待下章节分析,如果有分析不到位的请大家指正。