LockSupport.park() 和 unpark(Thread) 的底层原理涉及 JVM 与操作系统(OS)的线程调度机制,是 Java 并发编程中实现线程阻塞/唤醒的核心机制之一。下面详细讲解其底层实现原理。
🔍 一、核心概念:许可机制(Permit)
每个线程内部维护一个许可(permit),这是理解 park() 和 unpark() 行为的关键:
| 操作 | 许可变化 | 线程行为 |
|---|---|---|
| 初始状态 | permit = 0 | 不可运行 |
调用 unpark(Thread t) | permit = 1(最多为1) | 如果线程正在 park,则被唤醒 |
调用 park() | 如果 permit > 0 → permit -1,立即返回;否则线程进入等待状态 | 阻塞当前线程 |
⚠️ 注意:多次调用
unpark()同一线程不会叠加许可,只会保留一个(即 permit 最多为 1)。
🧠 二、底层实现原理详解
1. JVM 内部实现机制(以 HotSpot 为例)
LockSupport是通过 JVM 提供的 native 方法 实现的:private static native void park(boolean isAbsolute, long time);private static native void unpark0(Thread thread);
这些方法最终会调用操作系统的线程调度 API,如:
| 平台 | 底层机制 |
|---|---|
| Linux | 使用 pthread_cond_wait 或 futex 系统调用 |
| Windows | 使用 WaitOnAddress 或 Condition Variable |
| Solaris | 使用 lwp_park / lwp_unpark |
2. 线程挂起与恢复流程
✅ park() 的执行过程:
- 检查当前线程的 permit 值:
- 如果 permit == 1 → 清除 permit,立即返回。
- 如果 permit == 0 → 当前线程进入等待状态。
- 等待其他线程调用
unpark()或发生中断。 - 被唤醒后,继续执行后续代码。
✅ unpark(Thread thread) 的执行过程:
- 找到目标线程的 JVM 内部表示(
JavaThread*)。 - 尝试将目标线程的 permit 设置为 1。
- 如果目标线程正在
park(),则它会被唤醒。 - 如果目标线程尚未调用
park(),那么下次调用时可以直接返回(因为 permit 已经为 1)。
- 如果目标线程正在
📦 三、与 Object.wait() 的区别(底层视角)
| 对比项 | LockSupport.park/unpark | Object.wait/notify |
|---|---|---|
| 实现层级 | JVM + OS 调度支持(更底层) | 基于对象监视器(Monitor) |
| 是否依赖 synchronized | ❌ 否 | ✅ 是 |
| 是否可以跨线程精确控制 | ✅ 可以指定线程 | ❌ notifyAll() 或 notify() |
| 是否有虚假唤醒风险 | ✅ 有 | ✅ 有 |
| 是否清除中断标志 | park() 不清除中断标志 | wait() 抛出异常并清除中断标志 |
| 性能开销 | 更轻量级 | 相对较重(需要 Monitor 获取) |
🎯 四、为什么 unpark() 可以“先调用”?
这是 LockSupport 的一个重要特性,也是其优于 wait/notify 的地方。
unpark()会把目标线程的 permit 设置为 1。- 如果之后该线程调用
park(),由于 permit == 1,会直接返回而不会阻塞。 - 这意味着你可以在任意时机唤醒线程,而不必担心它是否已经进入等待状态。
💡 举例说明:
Thread t = new Thread(() -> {
LockSupport.park(); // 允许在 park 之前 unpark
});
t.start();
LockSupport.unpark(t); // 先 unpark
在这种情况下,线程 t 在执行 park() 时会立即返回,不会阻塞。
🧩 五、实际应用中的典型场景
1. AQS(AbstractQueuedSynchronizer)中的使用
AQS 中大量使用了 LockSupport 来实现线程的阻塞和唤醒:
acquireQueued():在线程获取锁失败时调用park()挂起自己。release():释放锁后调用unpark()唤醒同步队列中的下一个节点。
final boolean acquireQueued(final Node node, int arg) {
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
return false;
}
if (shouldParkAfterFailedAcquire(p, node))
LockSupport.park(this); // 挂起当前线程
}
} catch (InterruptedException ie) {
selfInterrupt();
return true;
}
}
2. ForkJoinPool 中的任务调度
ForkJoinPool 使用 unpark() 显式唤醒工作线程来处理任务,提高并发效率。
🧪 六、伪唤醒(Spurious Wakeup)问题
虽然不像 wait() 那样频繁,但 park() 也可能出现伪唤醒(Spurious Wakeup),即线程未被显式 unpark() 却从 park() 返回。
✅ 正确做法:
始终将 park() 放入循环中,并配合状态变量判断是否应该继续阻塞:
while (!canProceed()) {
LockSupport.park();
}
🧾 七、总结
| 特性 | 描述 |
|---|---|
park() | 阻塞当前线程,直到被 unpark() 或中断唤醒 |
unpark(Thread) | 唤醒指定线程,即使它尚未调用 park() 也能生效 |
| 许可机制 | 每个线程有一个 permit(0 或 1) |
| 可靠性 | 支持跨线程精确唤醒,避免 notify() 的不确定性 |
| 底层实现 | 依赖 JVM native 方法,调用 OS 线程调度接口 |
| 应用场景 | AQS、线程池、ForkJoin、自定义锁等并发框架 |