不该直接调用 Unsafe.park/unpark,因其无中断检查、无许可证累积、无 blocker 诊断、模块化受限;应使用 LockSupport,它提供安全、可诊断、可中断的 park/unpark 封装。

不能直接用 Unsafe.park 和 Unsafe.unpark 构建生产级同步组件 —— 它们是 JVM 底层原语,没有许可证累积保护、不检查中断状态、不设 blocker 诊断信息,且 Unsafe 类本身在 JDK 9+ 默认不可反射访问(模块系统限制),强行绕过会触发 IllegalAccessError 或启动失败。
为什么不该直接调用 Unsafe.park/unpark
直接调用 Unsafe.park 会跳过 LockSupport 层的所有安全封装:
-
Unsafe.park(false, 0)不感知线程中断,Thread.interrupt()后仍无限阻塞;而LockSupport.park()内部会检查中断位并提前返回 - 没有许可证预存逻辑:若先
Unsafe.unpark(t)再Unsafe.park(),许可证丢失,线程永久 WAITING(LockSupport能保证“unpark 先发也有效”) - 无法在 jstack 中显示阻塞对象(
blocker),排查时只能看到parking to wait for,无上下文 - JDK 17+ 默认禁止通过反射获取
Unsafe.getUnsafe(),需加--add-opens java.base/jdk.internal.misc=ALL-UNNAMED启动参数,违背模块化设计原则
真正该用的接口:LockSupport 的 park/unpark
LockSupport 就是专为“构建同步组件”设计的轻量级门面 —— 它把 Unsafe 的裸操作包装成线程安全、可诊断、可中断的稳定 API:
-
LockSupport.park()等价于Unsafe.park(false, 0),但自带中断检查和许可证缓存 -
LockSupport.parkNanos(long)使用纳秒级相对时间,精度高于Thread.sleep(),且唤醒后不抛InterruptedException,适合自旋+阻塞混合策略 -
LockSupport.park(Object blocker)传入锁对象(如this或Node实例),jstack 中显示为parking to wait for (a MyLock$Sync),问题定位一目了然 -
LockSupport.unpark(Thread)是唯一能精准唤醒指定线程的方法,比Object.notify()更可控,无虚假唤醒风险
在 AQS 风格同步器中如何正确使用
所有可靠自定义锁(如 ReentrantLock、CountDownLatch)都基于 LockSupport + FIFO 队列实现。关键点不是“怎么挂起”,而是“挂起前确保排队成功、唤醒时确保目标在线”:
- 阻塞前必须将当前线程封装为
Node加入 CLH 队列,并用 CAS 设置前驱节点的waitStatus = Node.SIGNAL,否则unpark可能唤醒错人 - 唤醒时不要直接
unpark(node.thread),要先用compareAndSetWaitStatus(node, Node.SIGNAL, 0)确保该节点仍处于等待态,避免重复唤醒或漏唤醒 - 不要在
park后立刻读共享变量 —— 必须配合 volatile 读或Unsafe.loadFence(),否则可能看到过期值(JVM 可能重排序) - 示例节选(简化版自旋+park):
while (!tryAcquire(arg)) {<br> if (shouldParkCurrentThread()) {<br> LockSupport.park(this); // this 作为 blocker<br> if (Thread.interrupted()) break;<br> }<br>}
性能边界与容易被忽略的细节
高频调用 park/unpark 本身开销极低(纳秒级),但真实瓶颈往往藏在别处:
- 每次
park都触发一次内核态的pthread_cond_wait,若线程在用户态频繁进出阻塞态(如短超时轮询),反而比纯自旋更慢 -
unpark不是“发送信号”,而是设置一个内存标记(Parker::_counter),若目标线程正在执行而非阻塞,该标记会被消费掉 —— 这就是许可证模型的本质,也是它比notify更可靠的原因 - 虚拟线程(Loom)下,
LockSupport.park不阻塞平台线程,但Unsafe.park行为未定义,切勿混用 - 永远不要假设
park返回就代表“已被 unpark”—— 它也可能因中断、超时、或 JVM 内部原因返回,必须重新校验条件(即 while 循环包裹)










