另外,上面分析 shouldParkAfterFailedAcquire 方法还对 CANCELLED 的状态进行了判断,那么
什么时候会生成取消状态的节点呢?
答案就在 cancelAcquire 方法中, 我们来看看 cancelAcquire到底怎么设置/处理 CANNELLED 的
private void cancelAcquire(Node node) { // 忽略无效节点 if (node == null) return; // 将关联的线程信息清空 node.thread = null; // 跳过同样是取消状态的前驱节点 Node pred = node.prev; while (pred.waitStatus > 0) node.prev = pred = pred.prev; // 跳出上面循环后找到前驱有效节点,并获取该有效节点的后继节点 Node predNext = pred.next; // 将当前节点的状态置为 CANCELLED node.waitStatus = Node.CANCELLED; // 如果当前节点处在尾节点,直接从队列中删除自己就好 if (node == tail && compareAndSetTail(node, pred)) { compareAndSetNext(pred, predNext, null); } else { int ws; // 1. 如果当前节点的有效前驱节点不是头节点,也就是说当前节点不是头节点的后继节点 if (pred != head && // 2. 判断当前节点有效前驱节点的状态是否为 SIGNAL ((ws = pred.waitStatus) == Node.SIGNAL || // 3. 如果不是,尝试将前驱节点的状态置为 SIGNAL (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) && // 判断当前节点有效前驱节点的线程信息是否为空 pred.thread != null) { // 上述条件满足 Node next = node.next; // 将当前节点有效前驱节点的后继节点指针指向当前节点的后继节点 if (next != null && next.waitStatus <= 0) compareAndSetNext(pred, predNext, next); } else { // 如果当前节点的前驱节点是头节点,或者上述其他条件不满足,就唤醒当前节点的后继节点 unparkSuccessor(node); } node.next = node; // help GC }看到这个注释你可能有些乱了,其核心目的就是从等待队列中移除 CANCELLED 的节点,并重新拼接整个队列,总结来看,其实设置 CANCELLED 状态节点只是有三种情况,我们通过画图来分析一下:
至此,获取同步状态的过程就结束了,我们简单的用流程图说明一下整个过程
获取锁的过程就这样的结束了,先暂停几分钟整理一下自己的思路。我们上面还没有说明 SIGNAL 的作用, SIGNAL 状态信号到底是干什么用的?这就涉及到锁的释放了,我们来继续了解,整体思路和锁的获取是一样的, 但是释放过程就相对简单很多了
独占式释放同步状态故事要从 unlock() 方法说起
public void unlock() { // 释放锁 sync.release(1); }调用 AQS 模版方法 release,进入该方法
public final boolean release(int arg) { // 调用自定义同步器重写的 tryRelease 方法尝试释放同步状态 if (tryRelease(arg)) { // 释放成功,获取头节点 Node h = head; // 存在头节点,并且waitStatus不是初始状态 // 通过获取的过程我们已经分析了,在获取的过程中会将 waitStatus的值从初始状态更新成 SIGNAL 状态 if (h != null && h.waitStatus != 0) // 解除线程挂起状态 unparkSuccessor(h); return true; } return false; }查看 unparkSuccessor 方法,实际是要唤醒头节点的后继节点
private void unparkSuccessor(Node node) { // 获取头节点的waitStatus int ws = node.waitStatus; if (ws < 0) // 清空头节点的waitStatus值,即置为0 compareAndSetWaitStatus(node, ws, 0); // 获取头节点的后继节点 Node s = node.next; // 判断当前节点的后继节点是否是取消状态,如果是,需要移除,重新连接队列 if (s == null || s.waitStatus > 0) { s = null; // 从尾节点向前查找,找到队列第一个waitStatus状态小于0的节点 for (Node t = tail; t != null && t != node; t = t.prev) // 如果是独占式,这里小于0,其实就是 SIGNAL if (t.waitStatus <= 0) s = t; } if (s != null) // 解除线程挂起状态 LockSupport.unpark(s.thread); }有同学可能有疑问:
为什么这个地方是从队列尾部向前查找不是 CANCELLED 的节点?
原因有两个: