ReentrantLock
相对于 synchronized 它具备如下特点
- 可打断
- 可以设置超时时间
- 可以设置为公平锁
- 支持多个条件变量
与 synchronized 一样,都支持可重入
基本用法
// 获取锁 reentrantLock.lock(); try { // 临界区 } finally { // 释放锁 reentrantLock.unlock(); }
可重入
可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁
如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住
可打断
我在进入 Blocked list 等待锁的过程中,别的线程可以用 interupt() 方法打断我的等待(sychronized不可打断,reentrantLock.lock() 不可打断,reentrantLock.lockInterruptibly() 才可以打断)
被打断后会抛出 InteruptException 异常
可以说是一种 被动地避免死等的手段
锁超时
lock.tryLock() 没有参数的话,如果没获得锁,立刻返回false
有超时参数的话,等待一段时间后还没获得锁,再返回false
ReentrantLock lock = new ReentrantLock(); Thread t1 = new Thread(() -> { log.debug("启动..."); try { if (!lock.tryLock(1, TimeUnit.SECONDS)) { log.debug("获取等待 1s 后失败,返回"); return; } } catch (InterruptedException e) { e.printStackTrace(); } try { log.debug("获得了锁"); } finally { lock.unlock(); } }, "t1"); lock.lock(); log.debug("获得了锁"); t1.start(); try { sleep(2); } finally { lock.unlock(); }
用 tryLock 解决哲学家就餐问题
@Override public void run() { while (true) { // 尝试获得左手筷子 if (left.tryLock()) { try { // 尝试获得右手筷子 if (right.tryLock()) { try { eat(); } finally { right.unlock(); } } } finally { left.unlock(); } } } }
公平锁
非公平锁:之前持有锁的线程释放了锁,那么所有在 entryList 的线程一拥而上,谁先抢到了谁就是主人,而不是谁先到谁就先得
公平锁:阻塞队列里的线程抢锁时,是按进入阻塞的时间先入先得的顺序获得锁
本意是为了减少饥饿问题,但是 tryLock 可能更好。一般不会使用公平锁,因为会降低并发度(后面原理会讲?)
ReentrantLock 默认是非公平锁,如果要是公平锁要显式设置
ReentrantLock lock = new ReentrantLock(true);
条件变量
synchronized 中也有条件变量,就是获取了 sychronized 获取锁后可以调用 .wait() ,当条件不满足时进入 waitSet 等待
ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比 synchronized 是那些不满足条件的线程都在一间休息室等消息
而 ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤醒
与 sychronized 配合的 wait() 类似,使用要点:
- await 前需要获得 ReentrantLock 锁
- await 执行后,会释放锁,进入 conditionObject 等待
- await 的线程被 .signal() .signalAll() 唤醒(或打断、或超时)时重新竞争 lock 锁
- 竞争 lock 锁成功后,从 await 后继续执行
用法:
lock 有一点就是一定要在 finally 里释放,lock 和 unlock 成对出现
static ReentrantLock roomlock = new ReentrantLock(); static Condition waitCigaretteQueue = lock.newCondition(); static Condition waitbreakfastQueue = lock.newCondition(); static volatile boolean hasCigrette = false; static volatile boolean hasBreakfast = false; public static void main(String[] args) { new Thread(() -> { try { roomlock.lock(); while (!hasCigrette) { try { waitCigaretteQueue.await(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("等到了它的烟"); } finally { lock.unlock(); } }).start(); new Thread(() -> { try { roomlock.lock(); while (!hasBreakfast) { try { waitbreakfastQueue.await(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("等到了它的早餐"); } finally { lock.unlock(); } }).start(); sleep(1); sendBreakfast(); sleep(1); sendCigarette(); } private static void sendCigarette() { roomlock.lock(); try { log.debug("送烟来了"); hasCigrette = true; waitCigaretteQueue.signal(); } finally { lock.unlock(); } } private static void sendBreakfast() { roomlock.lock(); try { log.debug("送早餐来了"); hasBreakfast = true; waitbreakfastQueue.signal(); } finally { lock.unlock(); } }
使用案例-交替打印
abcabcabcabcabcabc
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; public class TestThreadJTPrint2 { public static void main(String[] args) { // 循环次数, 五次。做为 AwaitSignal 的构造方法入参传入。AwaitSignal 继承 ReentrantLock AwaitSignal awaitSignal = new AwaitSignal(5); // 每个线程都有自己的休息室 Condition aCondition = awaitSignal.newCondition(); Condition bCondition = awaitSignal.newCondition(); Condition cCondition = awaitSignal.newCondition(); Thread t1 = new Thread(() -> { // 打印"a", myCondition 是 aCondition, nextCondition 是 bCondition awaitSignal.print("a", aCondition, bCondition); }); Thread t2 = new Thread(() -> { awaitSignal.print("b", bCondition, cCondition); }); Thread t3 = new Thread(() -> { awaitSignal.print("c", cCondition, aCondition); }); // 依次 start 的时候,都会先去自己的休息室等待 t1.start(); t2.start(); t3.start(); // t1 t2 t3 启动后,要等等待一段时间,等它们都到各自的休息室准备好。再由主线程唤醒a休息室里的a线程 // 不然会有错误 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // t1 t2 t3 依次 start 的时候,都会先去自己的休息室等待 // 所以要由主线程来先唤醒休a休息室里的线程。主线程也要先 lock 才能 signal() awaitSignal.lock(); try { // 注意是 signal() 不是 notifyAll() aCondition.signal(); } finally { // unlock 一定要在 finally 里 awaitSignal.unlock(); } } // 继承了 ReentrantLock 后 lock() 就相当于 lock(this) public static class AwaitSignal extends ReentrantLock { private int loopNumber; AwaitSignal(int loopNumber) { this.loopNumber = loopNumber; } public void print(String printContent, Condition myCondition, Condition nextCondition) { for (int i=0;i<loopNumber;i++) { // 因为 extends ReentrantLock,所以可以直接有这个lock方法,相当于lock住了this lock(); try { // 打印前都先去自己的休息室休息。有人来唤醒才会去下面真正打印 myCondition.await(); } catch (InterruptedException e) { e.printStackTrace(); } // 真正打印 System.out.println(printContent); // 唤醒下一个休息室的线程。注意是 signal() 不是 notifyAll() nextCondition.signal(); } } } }
原理
1.非公平锁加锁(CAS 修改 AQS 对象的 state 0-1表示锁。失败 park 并添加到阻塞链表)
public void lock() { sync.lock(); }
lock()
// 非公平锁 static final class NonfairSync extends Sync { /** * Performs lock. Try immediate barge, backing up to normal * acquire on failure. */ final void lock() { if (compareAndSetState(0, 1)) // 加锁成功,设置锁 owner 为当前线程就结束了。直接返回 setExclusiveOwnerThread(Thread.currentThread()); // 加锁失败 else acquire(1); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } }
acquire
会再试着加锁一次,如果再失败,就会进入等待队列
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
addWaiter
cas 获取锁(!tryAcquire(arg))失败后:addWaiter 将等待的线程加入链表中
参数:Node.EXCLUSIVE 表示当前是独占模式,即只有一个线程能够访问资源
private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); Node pred = tail; if (pred != null) { // 当前节点的 prev 指向之前的尾部节点 pred node.prev = pred; // CAS 设置链表 tail 变量为当前节点 node,期待前值为【pred】 if (compareAndSetTail(pred, node)) { // 之前的尾部节点 pred 的 next 指向当前节点 pred.next = node; return node; } } // pred==null 也就是链表没初始化 // 或者 CAS 设置 tail 为当前节点失败时 进入 enq(node); return node; }
enq
private Node enq(final Node node) { // 循环进行尝试 for (;;) { // 每次循环获取最新的尾部节点 Node t = tail; // 尾部节点为空,即链表还没初始化的情况 if (t == null) { // Must initialize // CAS 将链表 head 变量设为当前节点,期待前值是 null if (compareAndSetHead(new Node())) // 新链表,tail 也为当前节点 tail = head; } // addWaiter CAS 将链表的 tail 变量设为当前节点 失败时(也就是说 tail 被别的线程改掉了),会第一次走到这里,重新尝试 else { // 当前节点 prev 指向最新的尾部节点 t node.prev = t; // CAS 将链表的 tail 变量设为当前节点,期待前值为 t if (compareAndSetTail(t, node)) { // 之前的尾部节点 t 的 next 指向当前节点 t.next = node;
// 一定是添加到队列成功了,才会退出循环返回 return t; } } } }
acquireQueued
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { // 当前节点的前驱节点 final Node p = node.predecessor(); // 如果当前节点前一个节点是头节点,说明当前节点是等待队列里的第一个(头节点是虚的)
// 此时再尝试获取一次锁 tryAcquire if (p == head && tryAcquire(arg)) { // 如果获取成功了。把当前节点设为虚头节点(令 head=当前节点,并且 thread prev 置为 null) setHead(node); // 之前的头节点 p.next = null; // help GC // 失败标志位 failed = false; // 返回中断标志位 return interrupted; } // 检查当前节点是否需要阻塞 if (shouldParkAfterFailedAcquire(p, node) && // 在这里 park 等待。 parkAndCheckInterrupt()) // interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
parkAndCheckInterrupt
private final boolean parkAndCheckInterrupt() { LockSupport.park(this); return Thread.interrupted(); }
shouldParkAfterFailedAcquire
注意不要混淆两个 state
state 是一个 AQS 实例中的一个属性,可以用 0 没被锁,1 加了锁
在等待队列中的节点都是没获取到锁的线程,Node 有一个属性叫 waitStaus,0 是初始状态
/** waitStatus value to indicate thread has cancelled */ static final int CANCELLED = 1; /** waitStatus value to indicate successor's thread needs unparking */ static final int SIGNAL = -1; /** waitStatus value to indicate thread is waiting on condition */ static final int CONDITION = -2; /** * waitStatus value to indicate the next acquireShared should * unconditionally propagate */ static final int PROPAGATE = -3;
检查是否需要阻塞
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { // 获取前一个节点的状态 int ws = pred.waitStatus; // 如果等待队列中前一个节点在阻塞,所以这个也阻塞 park if (ws == Node.SIGNAL) return true;
// >0 表示取消状态(>0的只有一个就是0取消) if (ws > 0) { // 上一个节点取消, 那么重构删除前面所有取消的节点, 返回到外层循环重试 do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; }
// 前驱节点的 waitStatus 是【初始默认状态 0】,那么将前驱节点改为 -1。
// -1 表示它有责任唤醒它的后继节点,因为要 park,所以必须保证可以有人来唤醒它,这个来唤醒它的就是前驱节点
else { /* * waitStatus must be 0 or PROPAGATE. Indicate that we * need a signal, but don't park yet. Caller will need to * retry to make sure it cannot acquire before parking. */ compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }
可以看到失败后,还会进行好几次获取锁的尝试,在第四次失败后,才会真正 Park
双向链表,每个等待线程由它的前驱节点线程唤醒,所以前驱节点的 waitStatus 都是 -1
2.非公平锁解锁(锁重入计数减为0,改锁状态。unpark 后继节点,后继节点获得锁)
锁重入计数减为0,改 AQS 对象状态,exclusiveThread 置为 null 。 unpark 后继节点
// Sync 继承自 AQS static final class NonfairSync extends Sync { // 解锁实现 public void unlock() { sync.release(1); } // AQS 继承过来的方法, 方便阅读, 放在此处 public final boolean release(int arg) { // 尝试释放锁, 进入 ㈠ if (tryRelease(arg)) { // 队列头节点 unpark Node h = head; if ( // 队列不为 null
h != null && // waitStatus == Node.SIGNAL (-1)需要 unpark h.waitStatus != 0 ) { // 释放它的后继节点 unpark AQS 中等待的线程, 进入 ㈡ unparkSuccessor(h); } return true; } return false; } // ㈠ Sync 继承过来的方法, 方便阅读, 放在此处 protected final boolean tryRelease(int releases) { // state-- int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; // 支持锁重入, 只有 state 减为 0, 才释放成功 if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; } // ㈡ AQS 继承过来的方法, 方便阅读, 放在此处 private void unparkSuccessor(Node node) { // 如果状态为 Node.SIGNAL 尝试重置状态为 0 // 不成功也可以 int ws = node.waitStatus; if (ws < 0) { compareAndSetWaitStatus(node, ws, 0); } // 找到需要 unpark 的节点, 但本节点从 AQS 队列中脱离, 是由唤醒节点完成的 Node s = node.next; // 不考虑已取消的节点, 从 AQS 队列从后至前找到队列最前面需要 unpark 的节点 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); } }
unpark 后继节点后,后继节点继续 for 循环,会获得锁
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); // 解锁 unpark 后,下一个循环在这里获得锁 if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return interrupted; } // 在 parkAndCheckInterrupt 里 park 住 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } } private final boolean parkAndCheckInterrupt() { // 在这里 park 住 LockSupport.park(this); return Thread.interrupted(); }
3.可重入
有一个重入计数
- 加锁时:如果加锁失败,判断锁的 owner 是否是当前线程,如果是,重入计数加一。
- 解锁时:释放一次,计数减一。只有重入计数减为0,才代表解锁成功
static final class NonfairSync extends Sync { // ... // Sync 继承过来的方法, 方便阅读, 放在此处 final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { // 加锁,CAS 修改 AQS 对象的 state 从0到1。acquires 为1 if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } // 如果已经获得了锁, 线程还是当前线程, 表示发生了锁重入 else if (current == getExclusiveOwnerThread()) { // state++(acquires=1) int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
} // Sync 继承过来的方法, 方便阅读, 放在此处 protected final boolean tryRelease(int releases) { // state-- (释放一次,计数减一) int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; // 支持锁重入, 只有 state 减为 0, 才释放成功 if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; } }
4.可打断(park 时被打断了,不可打断-忽略等获取到锁再重新打断;可打断-直接抛出打断异常)
不可打断模式
默认情况下是不可打断的。在此模式下,即使它被打断,仍会驻留在 AQS 队列中,仅会设置一个一个打断标志,一直要等到获得锁后根据打断标志得知自己被打断了,才真正打断
// Sync 继承自 AQS static final class NonfairSync extends Sync { // ... private final boolean parkAndCheckInterrupt() { // 如果打断标记已经是 true, 则 park 会失效 LockSupport.park(this); // 如果被打断了,会停止 park 走到这里。interrupted 返回打断标记,并清除打断标记 return Thread.interrupted(); } final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; failed = false; // 还是需要获得锁后, 才能返回打断状态 return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) { // 被打断后 parkAndCheckInterrupt 返回 true,走到这里。只是设置了标记变量,其它什么都没干,没有抛出打断异常 interrupted = true; } } } finally { if (failed) cancelAcquire(node); } } public final void acquire(int arg) {
// 上面 acquireQueued 返回打断标记为 true 后,即被打断的线程在一直排队等待获得锁以后。会进到里面,重新打断一次 if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) { // 如果打断状态为 true selfInterrupt(); } } static void selfInterrupt() { // 重新产生一次中断 Thread.currentThread().interrupt(); } }
可打断模式
park 时被打断了,直接抛出打断异常
static final class NonfairSync extends Sync { public final void acquireInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); // 如果没有获得到锁, 进入 ㈠ if (!tryAcquire(arg)) doAcquireInterruptibly(arg); } // ㈠ 可打断的获取锁流程 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()) { // 在 park 过程中如果被 interrupt 会进入此 // 这时候抛出异常, 而不会再次进入 for (;;) throw new InterruptedException(); } } } finally { if (failed) cancelAcquire(node); } } }
5.公平锁(AQS state 为0时:非公平-直接CAS竞争,公平-先检查队列,没人排队或自己在最前面才去CAS竞争 )
非公平锁实现
如果 AQS 状态为0(无锁),就直接尝试用 CAS 获取锁,这里体现了非公平性,不去检查之前已经在排队的 AQS 队列
// ㈡ 进入 ㈢ protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } // ㈢ Sync 继承过来的方法, 方便阅读, 放在此处 final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); // 如果还没有获得锁 if (c == 0) { // 直接尝试用 cas 获得, 这里体现了非公平性: 不去检查之前已经在排队的 AQS 队列 if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } // 如果已经获得了锁, 线程还是当前线程, 表示发生了锁重入 else if (current == getExclusiveOwnerThread()) { // state++ int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } // 获取失败, 回到调用处 return false; }
公平锁实现
如果 AQS 状态为 0(无锁),不会先直接尝试用 CAS 获取锁,而时先检查之前已经在排队的 AQS 队列,如果没有人排队,或者自己就在最前面,才会尝试用 CAS 竞争锁
static final class FairSync extends Sync { private static final long serialVersionUID = -3000897897090466540L;
final void lock() { acquire(1); } // AQS 继承过来的方法, 方便阅读, 放在此处 public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) { selfInterrupt(); } }
// 与非公平锁主要区别在于 tryAcquire 方法的实现 protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { // 先检查 AQS 队列中是否有前驱节点, 没有才去竞争 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } // ㈠ AQS 继承过来的方法, 方便阅读, 放在此处 public final boolean hasQueuedPredecessors() { Node t = tail; Node h = head; Node s; // h != t 时表示队列中有 Node return h != t &&( // (s = h.next) == null 表示队列中还有没有老二 (s = h.next) == null || // 或者队列中老二线程不是此线程 s.thread != Thread.currentThread() ); } }
6.条件变量
每个条件变量其实就对应着一个等待队列(锁是一个公共的等待队列 等待获得锁。调用 condition.await(); 会到这个 condition 上去等待 signal)
其实现类是 ConditionObject
与 sychronized 配合的 wait() 类似,使用要点:
- await 前需要获得 ReentrantLock 锁
- await 执行后,会释放锁,进入 conditionObject 等待
- await 的线程被 .signal() .signalAll() 唤醒(或打断、或超时)时重新竞争 lock 锁
- 竞争 lock 锁成功后,从 await 后继续执行
await 流程
- 添加当前线程节点到 condition 等待队列
- 释放当前线程持有的锁(锁重入计数清零)
- 当前线程 park 阻塞住,等待被 signal 唤醒并重新竞争获得锁后恢复运行
// 等待 - 直到被唤醒或打断 public final void await() throws InterruptedException { if (Thread.interrupted()) { throw new InterruptedException(); }
// 添加一个 Node 至等待队列, 见 ㈠ Node node = addConditionWaiter();
// 释放节点持有的锁(锁重入计数全部清零) int savedState = fullyRelease(node); int interruptMode = 0;
// 如果该节点还没有转移至 AQS 队列, 阻塞 while (!isOnSyncQueue(node)) { // 执行 wait 后,前面释放了锁。在这里本线程 park 阻塞 LockSupport.park(this); // 如果被打断, 退出等待队列 if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; }
// 退出等待队列后, 还需要获得 AQS 队列的锁 if (acquireQueued(node, savedState) && interruptMode != THROW_IE) interruptMode = REINTERRUPT; // 所有已取消的 Node 从队列链表删除, 见 ㈡ if (node.nextWaiter != null) unlinkCancelledWaiters(); // 应用打断模式, 见 ㈤ if (interruptMode != 0) reportInterruptAfterWait(interruptMode); }
将当前线程加到 condition 链表尾部
private Node addConditionWaiter() { Node t = lastWaiter; // If lastWaiter is cancelled, clean out. if (t != null && t.waitStatus != Node.CONDITION) { unlinkCancelledWaiters(); t = lastWaiter; }
// 以当前线程新建一个节点 Node node = new Node(Thread.currentThread(), Node.CONDITION); if (t == null) firstWaiter = node; else t.nextWaiter = node;
// lastWaiter 是链表尾 lastWaiter = node; return node; }
fullyRelease
释放锁:最后 ownerThread 空出来了,AQS state 也会成为 0,还会 unpark 锁等待队列中的下一个节点
不管锁重入了几次,condition.wait() 的时候都要释放锁,要把锁重入计数清零
所以,一般 release(int) 方法传入的都是1,现在 getState() 后把重入计数都传进去,清零重入计数
final int fullyRelease(Node node) { boolean failed = true; try { // 获得锁重入计数 int savedState = getState(); // 一般 release 传入的都是1,现在传入重入计数,清零重入计数 if (release(savedState)) { failed = false; return savedState; } else { throw new IllegalMonitorStateException(); } } finally { if (failed) node.waitStatus = Node.CANCELLED; } }
release 唤醒锁等待队列中的下一个节点,去竞争锁
public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }
signal 流程
必须锁持有者才能调用 condition.signal() ,唤醒对应 condition 上的等待线程
// 唤醒 - 必须持有锁才能唤醒, 因此 doSignal 内无需考虑加锁 public final void signal() { // 不是锁持有者调用 signal,抛出异常 if (!isHeldExclusively()) throw new IllegalMonitorStateException(); Node first = firstWaiter; // 对 condition 等待队列中的第一个做 signal if (first != null) doSignal(first); }
- 因为唤醒第一个节点,所以把第一个节点从 condition 的等待队列链表中断开
- 要重新竞争锁,把这个节点加入到等待锁 AQS 队列队尾
transferForSignal 的返回值是从 condition 等待队列转移到 AQS 队列 成功与否。为什么会转移失败?
- 在中途,可能会被打断,或等待超时。那么就取消去竞争锁了,这时就会失败。
- 失败了,即 !transferForSignal(first) 条件就会成立,就会继续下一次循环,从 condition 等待队列中取下一个节点唤醒(first = firstWaiter)
private void doSignal(Node first) { do { // 已经是尾节点了 if ( (firstWaiter = first.nextWaiter) == null) { lastWaiter = null; } // 唤醒 condition 等待队列中的第一个节点,把它从这个队列中断开 first.nextWaiter = null; } while ( // 将等待队列中的 Node 转移至 AQS 队列,成功则不再循环。不成功且还有节点则继续循环 ㈢ !transferForSignal(first) && // 队列还有节点 (first = firstWaiter) != null ); }
- condition 等待队列中,节点状态初始都是 Node.CONDITION。先 CAS 修改节点状态为 0(锁等待队列中最后一个节点的状态是0),前值是 Node.CONDITION,如果当前已经不是 Node.CONDITION 了,会 CAS 失败返回 false。外层会从 condition 队列中再取下一个节点唤醒
- enq(node) 方法将节点加入 AQS 队列,即锁等待队列的尾部。这个方法会返回前驱节点。
- 锁等待队列的节点状态一般是:-1 -1 -1 0,除了最后一个节点是0,其它全部是 -1。要把返回的前驱节点,即之前的最后一个节点,的状态 CAS 改为 NODE.signal() ,即 -1,表示它有责任唤醒它后面这个刚刚加入的节点
- unpark 唤醒的这个线程,让它可以恢复运行重新竞争锁
// 外部类方法, 方便阅读, 放在此处 // ㈢ 如果节点状态是取消, 返回 false 表示转移失败, 否则转移成功 final boolean transferForSignal(Node node) { // condition 等待队列中,节点初始状态就是 Node.CONDITION // 如果状态已经不是 Node.CONDITION, 说明被取消了 if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) // 返回 false,外层会继续唤醒 condition 等待队列中的下一个节点 return false; // 加入 AQS 队列,即锁等待队列 的尾部 // 返回的值是它的前驱节点,即之前的队列尾部,状态是初始0: -1 -1 -1 0 Node p = enq(node); int ws = p.waitStatus; if ( // 上一个节点被取消 ws > 0 || // 上一个节点不能设置状态为 Node.SIGNAL(-1,表示有责任唤醒下个节点) !compareAndSetWaitStatus(p, ws, Node.SIGNAL) ) { // unpark 取消阻塞, 让线程重新同步状态 LockSupport.unpark(node.thread); } return true; }