LockSupport 与线程通信机制:从 park/unpark 到底层实现
在并发编程中,线程通信的核心就是“挂起与唤醒”。都必须在锁内使用,不够灵活。则是 JUC 的底层工具类,提供更底层、线程级别的挂起与唤醒机制,AQS 的核心原语FutureTask、ReentrantLock、Condition 等的基础实现是所有高层同步工具的“线程调度引擎”。问题答案要点Q1:LockSupport 的作用?提供底层线程挂起与唤醒机制,支撑 AQS、FutureTask 等组件
文章目录
《LockSupport 与线程通信机制:从 park/unpark 到底层实现》
一、前言:为什么要了解 LockSupport
在并发编程中,线程通信的核心就是“挂起与唤醒”。
传统方式如:
Object.wait()/notify()Condition.await()/signal()
都必须在锁内使用,不够灵活。
LockSupport 则是 JUC 的底层工具类,
提供更底层、线程级别的挂起与唤醒机制,
它是:
- AQS 的核心原语
- FutureTask、ReentrantLock、Condition 等的基础实现
一句话:
LockSupport是所有高层同步工具的“线程调度引擎”。
二、核心原理与工作机制
(1)主要方法
LockSupport.park(); // 挂起当前线程
LockSupport.park(Object blocker); // 挂起并记录阻塞对象
LockSupport.unpark(Thread t); // 唤醒指定线程
(2)工作逻辑
LockSupport 基于 许可证(Permit)机制:
- 每个线程默认有 0 个许可;
unpark()向线程发放一个许可;- 若线程随后调用
park(),则立即返回; - 若线程先调用
park(),则被阻塞直到获得许可。
小结:
park()→ 消费一个许可;unpark()→ 发放一个许可;
多次unpark()不会累积许可(最多 1 个)。
(3)与 Object.wait() 区别
| 对比项 | LockSupport | Object.wait() |
|---|---|---|
| 是否依赖锁 | ❌ 否 | ✅ 必须在 synchronized 内 |
| 唤醒方式 | unpark(线程) | notify()/notifyAll() |
| 唤醒目标 | 精确唤醒某个线程 | 唤醒等待队列中任意线程 |
| 底层机制 | Unsafe.park/unpark | JVM Monitor 机制 |
| 是否易用 | ✅ 更灵活 | ❌ 需锁配合 |
三、底层实现与 Unsafe 机制
(1)核心实现
LockSupport 内部依赖 Unsafe 类:
public static void park() {
Unsafe.getUnsafe().park(false, 0L);
}
public static void unpark(Thread thread) {
if (thread != null)
Unsafe.getUnsafe().unpark(thread);
}
(2)底层原理
Unsafe.park() 通过操作 操作系统级的线程阻塞队列 实现挂起:
- 当线程执行
park()时进入阻塞状态; - 操作系统不再调度它(节省 CPU);
- 当
unpark()调用时,系统唤醒该线程。
线程状态变化:
RUNNABLE → WAITING → RUNNABLE
这套机制比传统的自旋等待或 busy-wait 更高效、节能。
(3)JVM 层支持
JVM 内部通过 Parker 对象(C++ 实现)管理每个线程的许可状态。
源码位于 OpenJDK unsafe.cpp:
void Parker::park(bool isAbsolute, jlong time) {
// 线程进入等待队列
if (counter == 0)
_pthread_cond_wait(&cond, &mutex);
else
counter = 0;
}
四、在 AQS 与并发组件中的作用
LockSupport 是整个 JUC 框架的“阻塞唤醒引擎”。
所有基于 AQS 的同步器(如 ReentrantLock、Semaphore、FutureTask)
都在内部使用 park/unpark 控制线程状态。
(1)AQS 等待队列逻辑
当线程获取锁失败时:
if (!tryAcquire(arg)) {
addWaiter(Node.EXCLUSIVE);
LockSupport.park(this);
}
→ 线程被挂起
→ 当前线程进入等待队列
→ 当锁释放后,调用 unpark() 唤醒下一个线程。
(2)FutureTask 示例
在 FutureTask 的 get() 方法中:
while (state < COMPLETED) {
LockSupport.park(this);
}
任务完成时:
LockSupport.unpark(waiterThread);
所以,无论是锁竞争、条件等待还是异步结果获取,
最终都离不开LockSupport的线程调度机制。
五、常见用法与注意事项
(1)基本使用示例
Thread t = new Thread(() -> {
System.out.println("子线程等待许可...");
LockSupport.park();
System.out.println("子线程被唤醒!");
});
t.start();
Thread.sleep(1000);
System.out.println("主线程发放许可");
LockSupport.unpark(t);
输出:
子线程等待许可...
主线程发放许可
子线程被唤醒!
(2)先 unpark 后 park 也可生效
LockSupport.unpark(t); // 发放许可
t.start(); // 后启动线程
因为线程的“许可”是可记忆的(一次性),
线程启动后调用 park() 时会立即返回,不阻塞。
(3)避免误用
- 不可使用多次 unpark() 叠加(只保留一次许可);
- 不可用 interrupt() 代替 unpark()(会清除中断标志);
- 不应混用 wait/notify 与 park/unpark(不同机制)。
六、面试常考问题与总结
| 问题 | 答案要点 |
|---|---|
| Q1:LockSupport 的作用? | 提供底层线程挂起与唤醒机制,支撑 AQS、FutureTask 等组件。 |
| Q2:park/unpark 与 wait/notify 区别? | 前者不依赖锁,可精准唤醒指定线程;后者需 synchronized,唤醒随机线程。 |
| Q3:为什么 LockSupport 比 wait/notify 效率高? | 不依赖监视器锁,操作系统级挂起,避免上下文切换开销。 |
| Q4:park() 线程如何恢复? | 被 unpark() 唤醒、被中断、或发生虚假唤醒。 |
| Q5:多次 unpark 会积累许可吗? | 不会,只保留一个许可。 |
| Q6:AQS 为什么使用 LockSupport 而不是 wait? | 因为 AQS 不一定持有对象锁,且需要精确唤醒队列中的线程。 |
结语
LockSupport 是并发框架的“地基”,
所有线程等待与唤醒的实现最终都基于它完成。
理解 LockSupport,
就等于理解了 AQS、ReentrantLock、FutureTask、Condition 的运行本质。
下一篇,我将写——
《Condition 接口与等待唤醒机制:对标 Object.wait/notify 的精准控制》,
作为锁通信机制的最后篇,深入讲清“线程协作”的设计哲学。
更多推荐



所有评论(0)