LockSupport.park() 线程“卡住”是正常设计:它无条件挂起线程,依赖配对unpark()或中断唤醒;许可为二进制、不可累积、可预发,且中断优先于许可。

LockSupport.park() 为什么线程没反应?
直接调用 LockSupport.park() 后线程看似“卡住”,不是 bug,是设计如此:它不检查前置条件,也不抛异常,只安静挂起当前线程,直到被 unpark() 唤醒,或被中断。常见误用是没配对调用 unpark(),或者在非预期时机(比如还没 park 就 unpark)触发了许可发放——LockSupport 内部只维护一个二进制许可,多次 unpark() 等效于一次。
- 许可不可累积:连续两次
LockSupport.unpark(t),后续一次park()仍只会消耗一个许可并立即返回 - 许可可“预发”:线程 A 在线程 B 调用
park()前就unpark(B),B 的 park 会直接返回,不会挂起 - 中断优先级高于许可:若线程在 park 中被中断,会立即返回,且
Thread.interrupted()返回 true
如何安全地用 park/unpark 实现线程协作
典型场景是自定义锁或条件等待,必须避免“丢失唤醒”。核心原则是:状态检查 + park + 再检查,即经典的“while 循环等待”模式。不能只靠 park/unpark,必须配合一个 volatile 状态变量做 guard。
volatile boolean ready = false;
Thread t = new Thread(() -> {
while (!ready) {
LockSupport.park(); // 挂起,等 ready 变 true
}
System.out.println("go!");
});
t.start();
// 主线程稍后触发
try { Thread.sleep(100); } catch (InterruptedException e) {}
ready = true;
LockSupport.unpark(t); // 必须在状态更新后调用
- 必须用
while而非if:防止虚假唤醒或状态未真正就绪 -
unpark()要在修改共享状态之后调用,否则可能因许可提前发放而失效 - 不要依赖 park 的超时版本做精确调度,
parkNanos(long)的纳秒精度在 JVM 层不保证,实际延迟受系统时钟和调度影响
park 与 wait/notify、Condition 的关键区别
LockSupport 是 JVM 底层线程阻塞原语,不关联任何对象监视器,也不需要 synchronized 块。这意味着它更轻量、更灵活,但也更易出错——没有内置的条件检查和原子性保障。
-
wait()/notify()必须在 synchronized 块内调用,且隐式绑定到对象 monitor;park()无此限制,但也没有自动的状态同步语义 -
Condition.await()/signal()是高级封装,内部也基于LockSupport,但提供了 await 时自动释放锁、signal 时自动重入等安全机制 -
park()不会释放任何锁(哪怕当前线程持有 synchronized 锁或 ReentrantLock),这点极易被忽略——它只是挂起线程,不管上下文资源
调试 park/unpark 行为的实用技巧
当线程行为不符合预期,优先查三点:许可是否已消耗、线程是否被中断、JVM 是否处于 safepoint(某些 GC 场景下 park 可能延迟返回)。可用 jstack 查看线程状态:
立即学习“Java免费学习笔记(深入)”;
jstack
输出中 park 状态显示为 java.lang.Thread.State: WAITING (parking),而非 WAITING (on object monitor)。另外,可通过 Thread.getState() 在代码中确认:
System.out.println(t.getState()); // 输出 WAITING 表示已 park
- 用
Thread.interrupted()检查 park 返回是否由中断引起,而不是许可 - 避免在 finally 块里盲目
unpark():可能唤醒错误线程,或重复唤醒导致许可泄露 - 测试时加日志:在 park 前、unpark 前、park 返回后都打时间戳和线程名,比单纯看状态更可靠










