1、简介
相对于 synchronized 它具备如下特点
- 可中断
- 可以设置超时时间
- 可以设置为公平锁(可解决饥饿问题)
- 支持多个条件变量,synchronized 如果没有获取到锁,统一放到 WaitSet 等待(等烟的,等外卖的,统一在一个休息室),而 ReentrantLock 可以包含多个 WaitSet ,分别保存不满足某条件的线程(等烟的和等外卖的分别各自有个休息室)
- 与 synchronized 一样,都支持可重入
基本语法
// 获取锁
reentrantLock.lock();
try {
// 临界区
} finally {
// 释放锁
reentrantLock.unlock();
}
2、可重入
可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁,如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住
package com.phz.juc;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author 两米以下皆凡人
*/
@Slf4j
public class Test {
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
method1();
}
public static void method1() {
lock.lock();
try {
log.info("execute method1");
method2();
} finally {
lock.unlock();
}
}
public static void method2() {
lock.lock();
try {
log.info("execute method2");
method3();
} finally {
lock.unlock();
}
}
public static void method3() {
lock.lock();
try {
log.info("execute method3");
} finally {
lock.unlock();
}
}
}
如果是不可重入锁,那么 method2 将不会再执行,因为是连续的执行了 lock.lock(),然后再是连续的 lock.unlock()
3、可打断
如果需要锁可以被打断,就不能使用 lock 加锁了,而要使用 lockInterruptibly,表示可打断的加锁,如下代码,在 t1 获取锁之前,锁率先被主线程获取到,那么它将阻塞住,等了一秒钟,主线程直接给 t1 打断了,不再让他获取锁。
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.info("启动...");
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
log.info("等锁的过程中被打断");
return;
}
try {
log.info("获得了锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
log.info("获得了锁");
t1.start();
try {
TimeUnit.SECONDS.sleep(1);
t1.interrupt();
log.info("执行打断");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
4、锁超时
上面根据可打断的特性可以避免死锁,但是前提是有其他的线程去执行 interrupt 才行,所以 ReentrantLock 也支持带有超时时间的加锁,避免死等,主动退出,观察如下代码,主线程先于 t1 获取到了锁,导致其拿不到锁,注意这里使用的是 lock.tryLock() 方法,是无参的,也就是说如果没有获取到锁,立刻就返回
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.info("启动...");
if (!lock.tryLock()) {
log.info("获取立刻失败,返回");
return;
}
try {
log.info("获得了锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
log.info("获得了锁");
t1.start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
tryLock 方法也可以添加超时时间
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.info("启动...");
try {
if (!lock.tryLock(3, TimeUnit.SECONDS)) {
log.info("超时获取失败,返回");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
log.info("获取不到锁,返回");
return;
}
try {
log.info("获得了锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
log.info("获得了锁");
t1.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
5、哲学家就餐
有五位哲学家,围坐在圆桌旁。
- 他们只做两件事,思考和吃饭,思考一会吃口饭,吃完饭后接着思考。
- 吃饭时要用两根筷子吃,桌上共有 5 根筷子,每位哲学家左右手边各有一根筷子。
- 如果筷子被身边的人拿着,自己就得等待
@Slf4j
public class Test {
@SneakyThrows
public static void main(String[] args) {
Chopstick c1 = new Chopstick("1");
Chopstick c2 = new Chopstick("2");
Chopstick c3 = new Chopstick("3");
Chopstick c4 = new Chopstick("4");
Chopstick c5 = new Chopstick("5");
new Philosopher("苏格拉底", c1, c2).start();
new Philosopher("柏拉图", c2, c3).start();
new Philosopher("亚里士多德", c3, c4).start();
new Philosopher("赫拉克利特", c4, c5).start();
new Philosopher("阿基米德", c5, c1).start();
}
}
/**
* 筷子
*/
class Chopstick {
String name;
public Chopstick(String name) {
this.name = name;
}
@Override
public String toString() {
return "筷子{" + name + '}';
}
}
@Slf4j
class Philosopher extends Thread {
Chopstick left;
Chopstick right;
public Philosopher(String name, Chopstick left, Chopstick right) {
super(name);
this.left = left;
this.right = right;
}
private void eat() throws InterruptedException {
log.info("eating...");
TimeUnit.SECONDS.sleep(1);
}
@SneakyThrows
@Override
public void run() {
while (true) {
// 获得左手筷子
synchronized (left) {
// 获得右手筷子
synchronized (right) {
// 吃饭
eat();
}
// 放下右手筷子
}
// 放下左手筷子
}
}
}
问题出在 synchronized 在尝试获取锁的时候,如果没有获取到,那么将会无限制的等待下去,所以我们这里可以采用 ReentrantLock 来解决这个问题
@Slf4j
public class Test {
@SneakyThrows
public static void main(String[] args) {
Chopstick c1 = new Chopstick("1");
Chopstick c2 = new Chopstick("2");
Chopstick c3 = new Chopstick("3");
Chopstick c4 = new Chopstick("4");
Chopstick c5 = new Chopstick("5");
new Philosopher("苏格拉底", c1, c2).start();
new Philosopher("柏拉图", c2, c3).start();
new Philosopher("亚里士多德", c3, c4).start();
new Philosopher("赫拉克利特", c4, c5).start();
new Philosopher("阿基米德", c5, c1).start();
}
}
/**
* 筷子
*/
class Chopstick extends ReentrantLock {
String name;
public Chopstick(String name) {
this.name = name;
}
@Override
public String toString() {
return "筷子{" + name + '}';
}
}
@Slf4j
class Philosopher extends Thread {
Chopstick left;
Chopstick right;
public Philosopher(String name, Chopstick left, Chopstick right) {
super(name);
this.left = left;
this.right = right;
}
private void eat() throws InterruptedException {
log.info("eating...");
TimeUnit.SECONDS.sleep(1);
}
@SneakyThrows
@Override
public void run() {
while (true) {
// 尝试获得左手筷子
if (left.tryLock()) {
try {
// 尝试获得右手筷子
if (right.tryLock()) {
try {
eat();
} finally {
right.unlock();
}
}
} finally {
left.unlock();
}
}
}
}
}
6、公平锁
ReentrantLock 默认是不公平的,synchronized 释放锁后,所有的等待线程都有相同机会获得这把锁,谁先抢到算谁的,而不是不公平的按照谁先等谁先执行,我们也可以使用 ReentrantLock 的构造方法指定其是否公平
/**
* 使用给定的公平策略创建 ReentrantLock 的实例。
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
观察下面非公平锁代码(谁先来谁得)
@SneakyThrows
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock(false);
lock.lock();
for (int i = 0; i < 500; i++) {
new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " running...");
} finally {
lock.unlock();
}
}, "t" + i).start();
}
// 1s 之后去争抢锁
Thread.sleep(1000);
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " start...");
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " running...");
} finally {
lock.unlock();
}
}, "强行插入").start();
lock.unlock();
}
观察下方公平锁(不一定总能复现,所以循环增加到 5000 )
公平锁一般没有必要,会降低并发度,分析原理时会讲解
7、条件变量
synchronized 中也有条件变量,就是我们讲原理时那个 WaitSet 休息室,当条件不满足时进入 WaitSet 等待
ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比
- synchronized 是那些不满足条件的线程都在一间休息室等消息
- 而 ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤醒
@Slf4j
public class Test {
static ReentrantLock lock = new ReentrantLock();
static Condition waitCigaretteQueue = lock.newCondition();
static Condition waitBreakfastQueue = lock.newCondition();
static volatile boolean hasCigrette = false;
static volatile boolean hasBreakfast = false;
@SneakyThrows
public static void main(String[] args) {
new Thread(() -> {
try {
lock.lock();
while (!hasCigrette) {
try {
waitCigaretteQueue.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("等到了它的烟");
} finally {
lock.unlock();
}
}).start();
new Thread(() -> {
try {
lock.lock();
while (!hasBreakfast) {
try {
waitBreakfastQueue.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("等到了它的早餐");
} finally {
lock.unlock();
}
}).start();
TimeUnit.SECONDS.sleep(1);
sendBreakfast();
TimeUnit.SECONDS.sleep(1);
sendCigarette();
}
private static void sendCigarette() {
lock.lock();
try {
log.info("送烟来了");
hasCigrette = true;
waitCigaretteQueue.signal();
} finally {
lock.unlock();
}
}
private static void sendBreakfast() {
lock.lock();
try {
log.info("送早餐来了");
hasBreakfast = true;
waitBreakfastQueue.signal();
} finally {
lock.unlock();
}
}
}
正确使用姿势应该是:
- await 前,必须获得锁
- await 执行后,会释放锁,进入 conditionObject (WaitSet)等待
- await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁
- 竞争 lock 锁成功后,从 await 后继续执行
8、原理
8.1、非公平锁
8.1.1、加锁解锁流程
先从构造器开始看,默认为非公平锁实现(谁先抢到谁获得锁)
NonfairSync 继承自 Sync,Sync 继承自 AQS
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
- 没有竞争的时候,exclusiveOwnerThread 将被标记为 t1
8.1.1.1、加锁失败
- 此时 t2 来了,开始 CAS 希望将 state 从 0 改为 1,但是失败了,进入 acquire 方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
-
可以看到 acquire 又调用了自身的 tryAcquire 方法,但是此时肯定是失败的,就会进入 acquireQueued 方法,这个方法会先调用 addWaiter 创建一个 Node 节点对象,然后 acquireQueued 加入等待队列中去
-
Node 队列为一个双向链表,且每个节点默认正常状态 waitStatus 为 0,对于此双向链表的头部(head 指向)节点用来占位,不关联线程,称其为 Dummy(哑元)或哨兵
- 进入 acquireQueued 逻辑方法
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//拿到当前新创建的Node的前驱Node,现在也就是那个哨兵
final Node p = node.predecessor();
//如果自己是紧邻着 head(排第二位),那么再次 tryAcquire 尝试获取锁,当然这时 state 仍为 1,失败
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//如果失败了,先判断是否需要park,如果需要park,则执行parkAndCheckInterrupt
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
-
可以看到,代码本身会进行一个死循环,不断的尝试获得锁,失败后会进入 park 阻塞
-
此时进入 shouldParkAfterFailedAcquire 逻辑,将前驱 node,即 head 的 waitStatus 改为 -1,这次返回 false,其中 SIGNAL = -1 的含义便是,它有责任唤醒其后继节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
//Node.SIGNAL = -1
if (ws == Node.SIGNAL)
/*
* 该节点已经设置了状态,要求释放以发出信号,因此它可以安全地park。
*/
return true;
if (ws > 0) {
/*
* 前驱节点被取消。跳过前驱并指示重试。
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {//此时 ws = 0
/*
* waitStatus 必须为 0 或 PROPAGATE = -3。表示我们需要一个信号,但不要park。呼叫者需要重试以确保在park前无法获取锁。
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
- 再次进入循环,再次尝试获取锁,失败,再次进入 shouldParkAfterFailedAcquire ,此时前驱节点已经是 -1 了,直接返回 true,接着调用 parkAndCheckInterrupt,开始阻塞
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
- 此时再来一个线程,尝试获得锁,失败,又创建一个 Node 节点进入 acquireQueued 方法,现在其前驱节点不是 head 直接进入 shouldParkAfterFailedAcquire 方法
- 现在其前驱节点为 t2 状态还是 0 ,所以将其设置为 -1,接着本 Node 再次 park
- 再次来一个,将会变为这个样子
8.1.1.2、解锁竞争
- 此时 t1 释放锁,进入 unlock->release->tryRelease 方法,最终就是将状态设置为 0
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
//头节点已经被创建,且其后继有节点
if (h != null && h.waitStatus != 0)
//唤醒其后继节点
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
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)
//改为 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;
//从最后开始往前找,直到找到 node 节点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
//将node节点对应的线程唤醒
if (s != null)
LockSupport.unpark(s.thread);
}
- 队列的第一个节点线程,其本身还阻塞在 parkAndCheckInterrupt 方法,唤醒后,就开始重新循环,获得锁
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())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
- 如果此时在唤醒 t2 获得锁的过程中,又来了一个线程,其不在阻塞队列中,它直接先抢到了锁,那么 t2 只能重新进入 park 阻塞(非公平的体现)
8.2、可重入原理
直接看代码注释
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
//来自父类 Sync
return nonfairTryAcquire(acquires);
}
}
//父类Sync中的方法
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//同一个线程来了,肯定是获取不到锁的,如果加锁的线程和当前线程相同,表示发生了锁重入,则进入如下分支
else if (current == getExclusiveOwnerThread()) {
//state++
int nextc = c + acquires;
if (nextc < 0) // 溢出(谁入这么多啊)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
//释放锁的方法,也在父类Sync中
protected final boolean tryRelease(int releases) {
//释放锁,因为可重入,所以不能直接置为0
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//如果已经减为0了,才会真正释放锁
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
//只有释放完了才能解锁
return free;
}
8.3、可打断原理
因为 ReentrantLock 支持设置为不可打断模式 (lock 为不可打断,lockInterruptibly 为可打断模式)
8.3.1、不可打断模式
如果被打断了,就会调用 interrupted 方法,返回是否打断,然后还会重置打断标记
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
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; // help GC
failed = false;
//获得了锁,这时候返回自己是不是被打断过
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
//被打断了,就会将表示设置为true,此标记只有在自己真正获得了锁后才会用到
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//如果是被打断过,就进入此方法
selfInterrupt();
}
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
可以发现在此模式下,即使它被打断,仍会驻留在 AQS 队列中,一直要等到获得锁后方能得知自己被打断了
8.3.2、可打断模式
当调用 lockInterruptibly 方法后:
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
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())
//同样的地方,只不过如果被打断了,直接就抛出异常,停止 AQS 队列中继续等了,直接跳出死循环
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
8.4、公平锁
对于非公平锁,调用 tryAcquire 后,直接就开始抢锁了,没有管 AQS 队列中的其他线程
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//如果还没有占用
if (c == 0) {
//在占用锁之前,先判断是否有前驱节点,没有才会去竞争
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;
}
}
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
//尾巴
Node t = tail; // Read fields in reverse initialization order
//头
Node h = head;
Node s;
//头不等于尾,证明队列中有Node
return h != t &&
//表示队列中还没有老二
((s = h.next) == null ||
//或者队列中老二线程不是此线程
s.thread != Thread.currentThread());
}
8.5、条件变量ConditionObject
每个条件变量其实就对应着一个等待队列,其实现类是 ConditionObject
8.5.1、await
调用 await 便会调用 addConditionWaiter 创建一个新的 Node 节点
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//创建一个新的 Node
Node node = addConditionWaiter();
//为什么叫fully,因为锁是可以重入的,所以需要把所有的锁都释放掉
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
//Node.CONDITION = -2
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
//尾插法
t.nextWaiter = node;
lastWaiter = node;
return node;
}
- 一开始 t1 持有锁,然后调用 await 方法,进入 addConditionWaiter 方法,创建新的 Node ,状态为 -2,并将其关联到 t1,加入等待队列尾部
- 然后调用 fullyRelease 方法,为什么叫 fully,因为锁是可以重入的,所以需要把所有的锁都释放掉
final int fullyRelease(Node node) {
boolean failed = true;
try {
//拿到总state
int savedState = getState();
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
//唤醒阻塞队列中的其他节点
unparkSuccessor(h);
return true;
}
return false;
}
- 如果没有其他线程竞争锁,那么应该是 t2 线程获得锁
8.5.2、signal
现在,t2 要唤醒刚刚的 t2,会将当前等待队列的头节点转移到 AQS 等待队列中,如果转移失败,证明当前节点已经被取消执行,会继续找下一个节点,如果转移成功,就会解锁,让等待队列的其余节点开始竞争锁
public final void signal() {
//判断当前线程是否持有锁
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//找到等待队列的头节点
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
private void doSignal(Node first) {
do {
//将头节点去掉
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
//会将当前头节点转移到竞争锁的队列中,如果转移失败,就会继续找下一个节点
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
* 如果不能被转移,那就证明这个任务已经被取消了,就没有必要转移了
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
Node p = enq(node);
int ws = p.waitStatus;
//设置前一个节点状态为-1
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
//执行解锁操作
LockSupport.unpark(node.thread);
return true;
}