unparkSuccessor
private void unparkSuccessor(Node node) { /* * 如果状态是负数的(即可能需要signal),请尝试清除预期的signal。 如果失败或状态被等待线程更改,则OK。 */ int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); /* * 要unpark的线程保留在后继线程中,后者通常就是下一个节点。 但是,如果取消或显然为空,从尾部逆向移动以找到实际的未取消后继者。 */ Node s = node.next; // 如果下个节点为 null 或者 cancelled,就找到队列最开始的非cancelled 的节点 if (s == null || s.waitStatus > 0) { s = null; // 从尾部节点开始到队首方向查找,寻得队列第一个 waitStatus<0 的节点。 for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } // 如果下个节点非空,而且unpark状态<=0的节点 if (s != null) LockSupport.unpark(s.thread);
之前的addWaiter方法的节点入队并不是原子操作
标识部分可以看做是 tail 入队的原子操作,但是此时pred.next = node;尚未执行,如果这个时候执行了unparkSuccessor,就无法从前往后找了
在产生CANCELLED状态节点的时候,先断开的是 next 指针,prev 指针并未断开,因此也是必须要从后往前遍历才能够遍历完
7.2 releaseShared
以共享模式释放。 如果 tryReleaseShared(int) 返回true,则通过解除一个或多个线程的阻塞来实现。
arg 参数 - 该值传送给 tryReleaseShared(int),但并未实现,可以自定义喜欢的任何内容。
执行流程
- tryReleaseShared 尝试释放共享锁,失败返回 false,true 成功走2
- 唤醒当前节点的后续阻塞节点
doReleaseShared
共享模式下的释放动作 - 表示后继信号并确保传播(注意:对于独占模式,如果需要signal,释放仅相当于调用head的unparkSuccessor)。
private void doReleaseShared() { /* * 即使有其他正在进行的acquire/release,也要确保 release 传播。 * 如果需要signal,则以尝试 unparkSuccessor head节点的常规方式进行。 * 但如果没有,则将状态设置为 PROPAGATE,以确保释放后继续传播。 * 此外,在执行此操作时,必须循环以防添加新节点。 * 另外,与unparkSuccessor的其他用法不同,我们需要知道CAS重置状态是否失败,如果重新检查,则失败。 */ for (;;) { Node h = head; if (h != null && h != tail) { int ws = h.waitStatus; if (ws == Node.SIGNAL) { if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) continue; // 循环以重新检查 unparkSuccessor(h); } else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; // 在失败的CAS上循环 } if (h == head) // 如果头结点变了则循环 break; } }
8 中断处理
唤醒对应线程后,对应的线程就会继续往下执行。继续执行acquireQueued
方法以后,中断如何处理?
8.1 parkAndCheckInterrupt
park 的便捷方法,然后检查是否中断
再看回 acquireQueued 代码,不论 parkAndCheckInterrupt 返回什么,都会执行下次循环。若此时获取锁成功,就返回当前的 interrupted。
acquireQueued 为True,就会执行 selfInterrupt
8.2 selfInterrupt
该方法是为了中断线程。
获取锁后还要中断线程的原因:
当中断线程被唤醒时,并不知道被唤醒的原因,可能是当前线程在等待中被中断,也可能释放锁后被唤醒。因此通过 Thread.interrupted() 检查中断标识并记录,如果发现该线程被中断过,就再中断一次
线程在等待资源的过程中被唤醒,唤醒后还是会不断尝试获取锁,直到抢到锁。即在整个流程中,并不响应中断,只是记录中断的记录。最后抢到锁返回了,那么如果被中断过的话,就需要补充一次中断