Java读源码之ReentrantLock(2)

本文是 ReentrantLock 源码的第二篇,第一篇主要介绍了公平锁非公平锁正常的加锁解锁流程,虽然表达能力有限不知道有没有讲清楚,本着不太监的原则,本文填补下第一篇中挖的坑。

Java源码之ReentrantLock

源码分析 感知中断锁

如果我们希望检测到中断后能立刻抛出异常就用 lockInterruptibly 方法去加锁,还是建议用 lock 方法,自定义中断处理,更灵活一点。

ReentrantLock#lockInterruptibly

我们只需要把 ReentrantLock#lock 改成 ReentrantLock#lockInterruptibly 方法就可以获得内部检测中断的锁了

public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1); }

AbstractQueuedSynchronizer#acquireInterruptibly

主要流程和前文介绍的类似

public final void acquireInterruptibly(int arg) throws InterruptedException { // 一上来就检查下中断,中断直接异常,就没必要抢锁排队了 if (Thread.interrupted()) throw new InterruptedException(); if (!tryAcquire(arg)) doAcquireInterruptibly(arg); }

AbstractQueuedSynchronizer#doAcquireInterruptibly

和正常加锁唯一区别就是这个方法,但是定睛一看是不是似曾相识?最大区别就是把中断标识给去掉了,检测到中断直接抛异常

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); } }

AbstractQueuedSynchronizer#cancelAcquire

此方法很有东西,只保证该节点失效,然后延迟移出等待队列

private void cancelAcquire(Node node) { if (node == null) return; // 把节点里登记等待的线程去掉,完成这一步此节点已经没有作用了 node.thread = null; // 下面的三步其实可以放到一个CAS中,直接设置 CANCELLED 状态 ,拿前一个节点,predNext 也必然是自己,但是吞吐量就下来了 // 这里大神,没有这样做也是出于了性能考虑,因为我们已经把等待线程设置成 null 了,所以此节点已经没有任何意义,没有必要去保证节点第一时间被释放,只要设置好 CANCELLED 状态 // 就算后面 CAS 调整等待队列失败了,下次取消操作也会帮着回收。相应地代码复杂度提高了。 /* ----------------------------------------- */ // 找到自己前面第一个没取消的节点, Node pred = node.prev; while (pred.waitStatus > 0) node.prev = pred = pred.prev; // 主要是为了下面把链表接上 Node predNext = pred.next; // 这里逻辑上把当前节点的状态设置成取消,便于检测释放 node.waitStatus = Node.CANCELLED; /* ----------------------------------------- */ // 如果当前节点是尾节点,就把前一个没取消的节点设成新尾巴 if (node == tail && compareAndSetTail(node, pred)) { // 把新尾巴的 next 设置成空 compareAndSetNext(pred, predNext, null); } else { // 进到这里说明当前节点肯定不是尾节点了 int ws; // 条件1: 如果前一个非取消节点不是头,也就是还需要排队 // 条件2: 如果前一个节点为 SIGNAL,也就是说后面肯定还有线程等待被唤醒 // 条件3: 如果前一个节点也取消了,说明前一个节点也取消了,还没来得及设置状态 if (pred != head && ((ws = pred.waitStatus) == Node.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); } // 有点花里胡哨,直接 = null不行么, node.next = node; // help GC } }

来张图说明下,假如我们目前等待队列里有7个线程。我们尝试中断线程7我们假设 CAS 操作都能成功:

Java读源码之ReentrantLock(2)

等待条件锁

上篇文章看源码过程中,AQS中有个 CONDITION 状态没有研究

static final int CONDITION = -2;

ReentrantLock 中的 newCondition 等 Condition 相关方法正是基于 AQS 中的实现的,让我们先大致了解一波作用和用法

Condition简介

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

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