Java LockSupport.park与unpark的线程阻塞唤醒机制
LockSupport 是 Java 并发包 (java.util.concurrent.locks) 中最基础的线程阻塞原语,用于构建锁和其他同步工具。与传统的 Object.wait/notify 相比,它不需要获取监视器锁,能够更灵活地阻塞和唤醒指定线程。
一、 理解核心机制:许可 (Permit)
LockSupport 的核心在于一个名为“许可” (Permit) 的概念。可以把这个许可看作一张“通行证”。
- 默认状态:每个线程都有一个许可,默认状态是 0 (不可用)。
- park 行为:当线程调用
LockSupport.park()时,如果许可可用 (值为1),它会消耗掉这个许可 (变为0) 并立即返回;如果许可不可用 (值为0),线程会被阻塞。 - unpark 行为:当调用
LockSupport.unpark(Thread thread)时,它会将目标线程的许可设置为 1 (可用)。如果线程此前因调用 park 而被阻塞,它会被唤醒;如果线程尚未阻塞,这次调用保证了它下一次调用 park 时不会阻塞。
以下流程展示了 park 与许可的状态流转逻辑:
二、 基础阻塞与唤醒操作
编写 以下代码,演示最标准的“先阻塞后唤醒”流程。
创建 一个新线程,在线程内部 调用 LockSupport.park()。
启动 线程,此时主线程 休眠 1秒以模拟业务耗时。
调用 LockSupport.unpark(t) 为该线程提供许可。
public class BasicParkExample {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
System.out.println("子线程:准备开始阻塞...");
// 阻塞当前线程,等待许可
LockSupport.park();
System.out.println("子线程:已被唤醒,继续执行!");
});
t.start();
Thread.sleep(1000); // 模拟主线程忙
System.out.println("主线程:准备唤醒子线程");
LockSupport.unpark(t); // 唤醒 t 线程
}
}
注意:unpark 可以在 park 之前调用。如果先执行了 unpark,许可变为1,随后线程执行 park 时发现许可可用,会直接消费掉许可而不会阻塞。
三、 响应线程中断
LockSupport.park() 能够响应中断,但与 Object.wait() 不同,它不会抛出 InterruptedException。
运行 以下代码观察中断行为。
定义 一个子线程并在其中 调用 LockSupport.park()。
在主线程中 调用 t.interrupt() 中断子线程。
public class InterruptParkExample {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
System.out.println("子线程:开始阻塞");
LockSupport.park();
System.out.println("子线程:从park中返回");
// 检查中断状态
System.out.println("中断状态: " + Thread.currentThread().isInterrupted());
});
t.start();
Thread.sleep(2000);
System.out.println("主线程:发送中断信号");
t.interrupt();
}
}
执行 结果分析:
- 线程从
park返回。 Thread.currentThread().isInterrupted()返回true,说明中断标志位被保留。
关键特性:
- 如果不清除中断标志,下次再循环调用
LockSupport.park()时,因为线程中断状态仍然存在,park方法会直接返回,无法再次阻塞。 - 调用
Thread.interrupted()可以清除中断标志,使下次park能够正常阻塞。
四、 避免不可重入陷阱
LockSupport 的许可是不可累积的,且 不支持重入。如果同一个线程连续调用两次 park(),而中间只调用了一次 unpark(),第二次 park 将会导致永久阻塞(死锁)。
执行 以下代码验证不可重入性。
public class NonReentrantExample {
public static void main(String[] args) {
Thread current = Thread.currentThread();
// 提前发放一次许可
LockSupport.unpark(current);
System.out.println("步骤 1");
LockSupport.park(); // 消费许可,正常运行
System.out.println("步骤 2");
// 此时许可已被消耗,变为 0
LockSupport.park(); // 再次阻塞,且不会再有 unpark 唤醒它
System.out.println("步骤 3 (永远不会打印)");
}
}
切记:每一次 park 调用,都必须对应一次 unpark 调用。
五、 对比传统 wait/notify
为了更清晰地理解 LockSupport 的优势,下表对比了它与 Object.wait/notify 的区别。
| 特性 | Object.wait / notify | LockSupport.park / unpark |
|---|---|---|
| 锁要求 | 必须持有对象监视器锁 (synchronized) | 不需要持有任何锁 |
| 唤醒对象 | 只能随机唤醒一个线程或全部唤醒 | 可以精准唤醒指定的线程 |
| 调用顺序 | 必须先 wait,后 notify | 支持 park 后 unpark,也支持先 unpark 后 park |
| 中断响应 | 抛出 InterruptedException | 返回,不抛出异常,需手动检查状态 |
总结:
在使用 LockSupport 时,请记住它是一种基于“许可”的轻量级同步机制。它通过 Unsafe 类直接操作操作系统层面的线程状态,脱离了 JVM 监视器锁的束缚,是实现高性能并发组件(如 ReentrantLock, CountDownLatch)的基石。

暂无评论,快来抢沙发吧!