一文带你学会AQS和并发工具类的关系2 (3)

tryRelease(arg)该方法调用的是ReentrantLock中

protected final boolean tryRelease(int releases) { // 获取当前锁持有的线程数量和需要释放的值进行相减 int c = getState() - releases; // 如果当前线程不是锁占有的线程抛出异常 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; // 如果此时c = 0就意味着state = 0,当前锁没有被任意线程占有 // 将当前所的占有线程设置为空 if (c == 0) { free = true; setExclusiveOwnerThread(null); } // 设置state的值为 0 setState(c); return free; }

如果头节点不为空,并且waitStatus != 0,唤醒后续节点如果存在的话。
这里的判断条件为什么是h != null && h.waitStatus != 0?

因为h == null的话,Head还没初始化。初始情况下,head == null,第一个节点入队,Head会被初始化一个虚拟节点。所以说,这里如果还没来得及入队,就会出现head == null 的情况。

h != null && waitStatus == 0 表明后继节点对应的线程仍在运行中,不需要唤醒

h != null && waitStatus < 0 表明后继节点可能被阻塞了,需要唤醒

private void unparkSuccessor(Node node) { // 获取头结点waitStatus int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); // 获取当前节点的下一个节点 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; } // 如果当前节点的下个节点不为空,而且状态<=0,就把当前节点唤醒 if (s != null) LockSupport.unpark(s.thread); }

为什么要从后往前找第一个非Cancelled的节点呢?
看一下addWaiter方法

private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); Node pred = tail; if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } enq(node); return node; }

我们从这里可以看到,节点入队并不是原子操作,也就是说,node.prev = pred, compareAndSetTail(pred, node) 这两个地方可以看作Tail入队的原子操作,但是此时pred.next = node;还没执行,如果这个时候执行了unparkSuccessor方法,就没办法从前往后找了,所以需要从后往前找。还有一点原因,在产生CANCELLED状态节点的时候,先断开的是Next指针,Prev指针并未断开,因此也是必须要从后往前遍历才能够遍历完全部的Node
所以,如果是从前往后找,由于极端情况下入队的非原子操作和CANCELLED节点产生过程中断开Next指针的操作,可能会导致无法遍历所有的节点。所以,唤醒对应的线程后,对应的线程就会继续往下执行。

4.2 释放锁流程图

图片

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

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