locksupport.park阻塞线程但不释放锁,与wait本质区别在于无需monitor;unpark发放不可叠加的二元permit,支持唤醒前置;blocker仅用于调试;park不响应中断,需手动检查thread.interrupted()。

LockSupport.park 会阻塞当前线程,但不释放锁资源
这是它和 Object.wait() 最关键的区别:park 不依赖任何 monitor,也不要求调用前持有 synchronized 锁。你可以在任意上下文里直接调用 LockSupport.park(),线程会进入 WAITING 状态,且不会干扰当前持有的任何锁(比如 ReentrantLock 或 synchronized 块里的锁)。这使得它成为构建非阻塞同步器(如 AQS)的理想原语。
常见错误是误以为 park 会像 wait 那样自动释放锁 —— 它完全不会。如果你在 synchronized 块里 park,那个 monitor 依然被占着;如果在 lock.lock() 后 park,锁也依然没被 unlock。必须手动管理锁的获取与释放。
unpark(Thread) 可以提前发放“许可”,且许可不可叠加
LockSupport.unpark(t) 的作用是把目标线程 t 的内部 permit 置为 1。这个 permit 是二元的:只有 0 和 1,不会累加。即使连续调用三次 unpark(t),t 下一次 park() 仍只消耗一个 permit,之后再次 park() 就会真正阻塞。
这个特性让调用顺序变得宽松:unpark() 可以在 park() 之前执行,线程不会丢失唤醒信号。而 wait()/notify() 要求 notify 必须在 wait 阻塞后发生,否则信号丢失 —— 这是自定义同步器选 park/unpark 的核心原因。
注意点:
-
unpark(null)会静默忽略,不抛异常 - 对已终止线程调用
unpark()无效,但也不会报错 - permit 不绑定对象或锁,只绑定线程实例本身
blocker 参数用于调试,不是同步逻辑的一部分
所有带 Object blocker 参数的 park 方法(如 park(Object)、parkNanos(Object, long)),其 blocker 仅用于记录线程阻塞原因,供 LockSupport.getBlocker(Thread) 查询,或在线程 dump 中显示。它不参与 permit 的发放/消费逻辑,也不影响阻塞行为。
实际开发中,建议传入能标识同步点的对象,比如:
- 当前锁实例(
this) - 某个 state holder 对象(
stateRef) - 字符串字面量(
"MySpinLock#acquire")
这样当 jstack 看到 WAITING 状态线程时,能快速定位是哪个组件卡住了。
park 无法响应中断,需手动检查 Thread.interrupted()
LockSupport.park() 不会抛出 InterruptedException,也不会自动清除中断状态。这意味着:如果线程在 park 前已被中断,park 会直接返回(不阻塞);如果在 park 中被中断,park 也会返回,但中断状态仍为 true。
所以正确写法是:
LockSupport.park();
if (Thread.interrupted()) {
// 处理中断:比如清理资源、抛出 InterruptedException、或向上层传递
throw new InterruptedException();
}
容易踩的坑:
- 忽略中断检查,导致线程无法被正常中断退出
- 用
Thread.currentThread().isInterrupted()替代Thread.interrupted(),导致中断状态残留,影响后续逻辑 - 在循环 park 场景中,只检查一次中断,而没放在每次 park 后
真正难的是把 permit 管理、状态跃迁、中断处理、以及公平性/超时等逻辑组合成可重入、可中断、支持条件队列的完整同步器。AQS 已经把这些封装好了;如果你真要从零写,重点不是 park/unpark 怎么调,而是怎么用它们稳住线程状态机 —— 比如 permit 消耗后如何保证不重复发放,如何避免虚假唤醒,以及如何让 unpark 精准命中等待中的线程而非已退出的线程。










