JDK8|LockSupport

LockSupport 是一个用来创建锁和其他同步工具类的基本线程阻塞原语

使用

说到线程阻塞和唤醒自然而然就会拿他和 Object.wait()/Object.notify() 进行对比

我们直接上代码

任何地方都可以使用

以 wait & notify 举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public static void waitNotify() throws InterruptedException {

Object lock = new Object();

Thread waitThread = new Thread(() -> {
synchronized (lock) {
try {
lock.wait(); //释放锁并进入阻塞态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("waitThread get notified");
}, "waitThread");

waitThread.start();

//确保waitThread先拿到锁
Thread.sleep(500L);

Thread notifyThread = new Thread(() -> {
System.out.println("notifyThread notify waitThread");
synchronized (lock) {
lock.notify();
}
}, "notifyThread");
notifyThread.start();

}

换成 LockSupport

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void parkUnpark() throws InterruptedException {

Thread parkThread = new Thread(() -> {
System.out.println("parkThread blocked by park");

LockSupport.park();

System.out.println("parkThread notified by unparkThread");
}, "parkThread");
parkThread.start();

//确保parkThread先拿到锁
Thread.sleep(500L);

Thread unparkThread = new Thread(() -> {

System.out.println("unparkThread notify parkThread");

LockSupport.unpark(parkThread);

}, "unparkThread");
unparkThread.start();

}

可以发现 LockSupport 不像是 wait/notify 那样必须要在 synchronized 下才能使用

无唤醒顺序依赖

此外,LockSupport 相比于 wait/notify ,还解决了顺序问题,看下面这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public static void unorderedWaitNotify() throws InterruptedException {

Object lock = new Object();

Thread waitThread = new Thread(() -> {
//模拟先notify再wait
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (lock) {
try {
lock.wait(); //释放锁并进入阻塞态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("waitThread get notified");
}, "waitThread");
waitThread.start();

Thread notifyThread = new Thread(() -> {
System.out.println("notifyThread notify waitThread");
synchronized (lock) {
lock.notify();
}
}, "notifyThread");
notifyThread.start();

}

上面这段代码,我们模拟先 notify 再 wait,结果就是 waitThread 永远地阻塞住了,程序无法正常结束

但是如果换用 LockSupport

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

public static void unorderedParkUnpark() throws InterruptedException {

Thread parkThread = new Thread(() -> {
//先unpark再park
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("parkThread blocked by park");

LockSupport.park();

System.out.println("parkThread notified by unparkThread");
}, "parkThread");
parkThread.start();



Thread unparkThread = new Thread(() -> {

System.out.println("unparkThread notify parkThread");

LockSupport.unpark(parkThread);

}, "unparkThread");
unparkThread.start();

}

我们先 unpark 再 park,结果可以发现虽然 unparkThread 先执行了 unpark 尝试唤醒,但是此时 parkThread 还在睡,还没 park 操作。但是不影响,2s 后 parkThread 调用 park 后立刻就被唤醒了

这也是 park/unparkwait/notify 的另一个核心区别:不论调用顺序,只要成对出现,必然可以唤醒线程

中断状态处理

在使用层面,wait() 会抛出 InterruptedException 需要手动 catch 处理,但是 park() 不会,park() 确实会响应线程中断,但是只是不抛出异常。除此之外 park() 中断后不会 clear 中断状态,而 wait() 中断后抛出异常会 clear 中断状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void waitInterrupted() {

Object lock = new Object();

Thread waitThread = new Thread(() -> {
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
//中断后被Catch,中断状态被clear
System.out.println("park thread interrupted, interrupted: " + Thread.currentThread().isInterrupted());
}
}
}, "waitThread");

waitThread.start();
waitThread.interrupt();
}
1
2
3
4
5
6
7
8
9
10
11
12
public static void parkInterrupted() {
Thread parkThread = new Thread(() -> {

LockSupport.park();
//中断后 park 方法立刻返回,中断状态未被clear
System.out.println("park thread interrupted, interrupted: " + Thread.currentThread().isInterrupted());

}, "parkThread");

parkThread.start();
parkThread.interrupt();
}

为什么不会清除中断状态

那么为什么 wait() 中断后会清除状态而 park() 中断后不会清除状态呢?

原因需要从 wait() 以及 park() 使用和设计的角度出发:

  • wait() 面向程序业务编写者,抛出的 InterruptedException 就是在强制要求处理中断后的逻辑,清除状态是为了后续业务逻辑能正常运行
  • park() 面向上层工具(例如 ReentrantLock),保留中断状态是为了供上层工具调用 Thread.currentThread().interrupted() 查询中断状态后进行定制化的进一步处理(例如告警、忽略、抛出异常)

还是看代码

对于 wait,就像是餐厅等餐

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class WaitScenario {
// 共享锁对象(相当于餐厅的取餐台)
static Object foodCounter = new Object();
// 标记餐是否做好
static boolean foodReady = false;

public static void main(String[] args) throws InterruptedException {
// 顾客线程(等待取餐)
Thread customer = new Thread(() -> {
synchronized (foodCounter) {
try {
// 没做好就等
while (!foodReady) {
System.out.println("顾客:餐还没好,我先等会儿...");
foodCounter.wait(); // 等待时可能被中断(接到紧急电话)
}
System.out.println("顾客:拿到餐了,走人~");
} catch (InterruptedException e) {
// 接到紧急电话,必须显式处理中断
System.out.println("顾客:接到紧急电话,不等了!");
// 关键:此时中断状态已被清除(wait()自动做的)
System.out.println("中断状态(清除后):" + Thread.currentThread().isInterrupted()); // false
}
}
}, "顾客");

customer.start();
// 模拟500ms后,顾客接到紧急电话(中断)
Thread.sleep(500);
customer.interrupt();
}
}

在这个场景中,wait()的核心是 “等待某个条件(餐做好)”,中断的含义是 “外部要求停止等待”

在中断后如果不清除中断状态,假设顾客中断后又想 “重新等待”(比如电话是误报)(其实就对应线程后续可能存在的业务逻辑),后续的wait()会因为 “中断状态残留” 直接失败(因为wait()会检查中断状态,有残留就直接抛异常)

而对于 park,更像是用在客制化锁的锁芯设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import java.util.concurrent.locks.LockSupport;

public class ParkScenario {
// 门禁锁(用park/unpark实现)
static class DoorLock {
// 记录当前持有锁的线程(谁刷脸成功了)
Thread owner;

// 上锁(门外的人等待)
public void lock() {
// 如果不是自己持有锁,就一直等
while (!Thread.currentThread().equals(owner)) {
System.out.println(Thread.currentThread().getName() + ":在门外等...");
LockSupport.park(); // 等待时可能被撬锁(中断)

// 关键:park()被中断后,保留中断状态,让锁判断如何处理
if (Thread.currentThread().isInterrupted()) {
// 这里可以灵活选择:抛异常/忽略/记录日志
System.out.println("门禁系统:检测到撬锁!");
// 清除状态(可选,根据需求)
Thread.interrupted();
}
}
}

// 解锁(刷脸成功)
public void unlock() {
owner = null;
}
}

public static void main(String[] args) throws InterruptedException {
DoorLock door = new DoorLock();

// 路人甲线程(想进门)
Thread personA = new Thread(() -> {
door.lock(); // 调用lock,内部会park
System.out.println("路人甲:进门了~");
}, "路人甲");

personA.start();
// 模拟500ms后有人撬锁(中断)
Thread.sleep(500);
personA.interrupt();
}
}

在这个场景中,park()是底层工具,用来实现 “阻塞等待”,而上层的DoorLock需要自主决定如何处理异常情况(比如撬锁)

如果park()清除了中断状态,DoorLocklock()方法就无法知道 “线程被中断过”(即 “被撬过”),也就无法执行报警、日志等逻辑 —— 灵活性完全丧失

保留状态后,上层工具(DoorLock)可以通过isInterrupted()查询到 “被中断过”,再根据需求自定义处理(比如抛异常、重试、忽略),这正是park()作为 “底层原语” 的设计目的

简单说:wait()是给 “直接用它写业务” 的人用的,必须强制处理中断并清状态,避免业务混乱;park()是给 “造工具” 的人用的,保留状态才能让工具更灵活

总结

通过 LockSupport 的 park/unparkwait/notify 的对比,我们可以简单总结:他们都可以实现线程的等待和唤醒。但是区别在于

特性 wait()/notify() LockSupport.park()/unpark()
同步要求 必须在synchronized块中调用 无任何同步要求,可在任意位置调用
唤醒顺序依赖 必须先waitnotify,否则失效 顺序无关,unpark可提前 “预支” 许可
唤醒目标 notify()随机唤醒一个等待线程 unpark(thread)精确唤醒指定线程
中断响应 抛出InterruptedException,清除中断状态 不抛异常,保留中断状态
底层机制 依赖对象监视器(monitor) 依赖 “许可”(permit)机制

而对于中断状态的处理,二者的处理逻辑不同

场景 wait()的逻辑(餐厅取餐) park()的逻辑(门禁锁开发)
使用者 业务开发者(直接处理等待 / 唤醒) 工具类开发者(用它构建更复杂的锁 / 同步工具)
中断的含义 “停止等待”(必须显式处理,否则业务会出错) “异常情况”(需要留给工具类自主决定如何处理)
状态处理的必要性 清除状态:避免影响后续的等待逻辑(比如重新等待) 保留状态:让上层工具能感知异常,实现灵活处理(报警 / 忽略等)

JDK8|LockSupport
http://example.com/2025/08/13/JDK8-LockSupport/
作者
Noctis64
发布于
2025年8月13日
许可协议