node.predecessor();
方法获取前节点,之前我们有看到 aqs 是必须要初始化的,通常情况下都不是 null 的。
final Node predecessor() throws NullPointerException { Node p = prev; if (p == null) throw new NullPointerException(); else return p; }
shouldParkAfterFailedAcquire()
shouldParkAfterFailedAcquire(p, node)
方法, 简单的讲就是把[node] 的有效前驱(有效是指node不是CANCELLED的)找到,并且将有效前驱的状态设置为SIGNAL
,之后便返回true代表马上可以阻塞了。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { // 获取前驱节点的状态 int ws = pred.waitStatus; // 如果是 SIGNAL 状态,即等待被占用的资源释放,直接返回 true // 准备继续调用 parkAndCheckInterrupt 方法 if (ws == Node.SIGNAL) return true; // ws 大于 0 说明是 cancelled 状态 if (ws > 0) { // 循环判断节点的前驱节点是否也是 cancelled 状态,忽略该节点重新链接队列 do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { // 将当前节点的前驱节点设置为 SIGNAL 状态,用于后续唤醒操作 // 程序第一次执行到这里返回为 false,还会进行外层第二次循环,最终从代码第 7 行返回 compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }
parkAndCheckInterrupt(),
parkAndCheckInterrupt()
阻塞当前线程, 就是当线程多次获取锁失败后,进入 AQS 阻塞队列中进入阻塞等待,等持有者释放资源,激活当前线程。
private final boolean parkAndCheckInterrupt() { // 线程挂起,程序不会继续向下珍惜i给你 LockSupport.park(this); // 根据 park 方法 api 描述,程序下面三情况会继续向下执行 // 1. 调用 unpark // 2. 被中断 (interrupt) // 3. 其他不合逻辑的返回才会继续向下执行 // 因上述三种情况程序执行至此,返回当前线程的中断状态,并清空中断状态 // 如果由于被中断,该方法会返回 true return Thread.interrupted(); }
unlock 方法
非公平锁方式解锁
解锁过程:
1、 unlock
方法源代码入,内部调用 release 方法并且传入一个 1 ,其实可以理解为归还/释放一个这把锁。
2、release
方法中有两个操作:第一步是执行 tyRelease
方法进行真正的解锁,如果解锁失败返回 false
返回, 第二部如果解锁成功,会去判断 AQS 中是否有等待的节点(即等待的线程)如果有就调用 unparkSuccessor
去 unpark 队列中第一个等待的线程。
3、我们先来看 tryRelease
方法, 首先是获取 state
变量的值,然后通过getState() - releases
来获取解锁后的值,再此之前还有一个判断就是判断的当前锁的持有者是否是当前线程,如果不是将抛出:IllegalMonitorStateException
异常;然后解锁后 state == 0
这个状态我们可以理解为 “完全解锁“ (其实这个是相对于锁重入来说的)。如果完全解锁就清空当前锁的线程持有者。然后在通过 cas 修改 state
的值。最终返回接锁后的布尔值。 其实这里有个小细节就是只有在 state = 0 的时候解锁的布尔值才会返回 true.
protected final boolean tryRelease(int releases) { // state -1 int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { free = true; // 清空锁持有者 setExclusiveOwnerThread(null); } // cas 修改 state setState(c); return free; }
4、unparkSuccessor(h);
方法主要是实现 AQS 中节点的唤醒操作, 它接受的一个参数就是我们在第二个步骤的时候传入的 node 节点,其实就是 AQS 的对头节点。这里通常就在头节点的下一个节点 , 如果没有找到会通过尾节点向前查找。最终确定需要唤醒的排队节点 s
执行 LockSupport.unpark(s.thread)
方法进入前面的抢锁的逻辑。
private void unparkSuccessor(Node node) { /* * If status is negative (i.e., possibly needing signal) try * to clear in anticipation of signalling. It is OK if this * fails or if status is changed by waiting thread. */ int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); /* * Thread to unpark is held in successor, which is normally * just the next node. But if cancelled or apparently null, * traverse backwards from tail to find the actual * non-cancelled successor. */ Node s = node.next; if (s == null || s.waitStatus > 0) { s = null; for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } if (s != null) // 排队节点唤醒 LockSupport.unpark(s.thread); }
ReentrantLock 总结
流程图梳理:
其实我刚开始看这块的时候,还事比较乱的,大家可以按照自己的思路,结合 ReentrantLock
源码尝试去分析,才能逐步的清晰和掌握。