park/unpark 不是 wait/notify 的替代品,因二者抽象层级不同:前者是无锁、线程级许可机制的底层原语,后者依赖对象监视器且必须在 synchronized 块内调用。

park/unpark 为什么不是 Object.wait/notify 的替代品
它们根本不在一个抽象层级上:LockSupport.park() 是 JVM 线程阻塞的底层原语,不关联任何对象监视器(monitor),也不需要 synchronized 块;而 Object.wait() 必须在已获取该对象锁的前提下调用,否则抛 IllegalMonitorStateException。
常见错误现象:直接把 park() 当成“更轻量的 wait()”来用,结果发现唤醒失效、线程卡死,或者和 synchronized 混用导致死锁逻辑混乱。
-
park()不检查调用上下文,无锁也能调,但也没法保证唤醒顺序或配对关系 -
unpark(Thread)可以提前调用,效果会“累积”——哪怕目标线程还没 park,下次 park 会直接返回,这是 wait/notify 完全不具备的特性 - 没有超时重试机制的裸
park()调用极易造成永久挂起,必须配合中断或超时判断
LockSupport.park() 底层到底做了什么
它最终调用的是 JVM 内部的 Unsafe.park(),后者在 HotSpot 中会转入平台相关代码(Linux 下是 pthread_cond_wait() + pthread_mutex_lock() 组合,但封装得极薄);关键点在于:JVM 为每个 Java 线程维护了一个独立的「许可(permit)」状态,初始为 0。
执行 park() 时:若 permit == 1,则原子置 0 并立即返回;否则线程被挂起,等待 permit 变为 1。执行 unpark(t) 时:将 t 的 permit 置为 1(如果尚未为 1)。
- permit 是二值的(0 或 1),多次
unpark()和一次等效,不会叠加 - 没有“队列”概念,
unpark()对已结束的线程无效,也不会报错 - 线程被
park()挂起后,仍可被interrupt()中断,此时会清空 permit 并抛InterruptedException
为什么 AQS 用 park/unpark 而不用 wait/notify
因为 AQS 的同步逻辑依赖“精准唤醒指定线程”,而 notify() 只能随机唤醒一个等待者,notifyAll() 又太重;park()/unpark(Thread) 允许你明确控制哪个线程该醒、什么时候醒,且无需共享锁对象。
典型使用场景:ReentrantLock 的公平锁模式中,头结点出队后,AQS 直接对下一个节点对应的线程调用 LockSupport.unpark(node.thread),跳过所有中间竞争逻辑。
- AQS 节点入队时并不 park,而是先尝试 CAS 获取状态,失败后再
park()—— 这种“乐观尝试 + 懒挂起”是 wait/notify 做不到的 - wait/notify 的 monitor 锁绑定在线程栈帧上,无法跨方法传递;而 permit 属于线程级状态,可在任意位置
unpark() - 性能上:park/unpark 避免了 monitor 的 enter/exit 开销,在高竞争下更可控
实际写代码时最容易漏掉的三件事
写自定义同步器或调试线程挂起问题时,这几个点几乎必踩:
- 忘记检查
Thread.interrupted()——park()返回后不判断中断状态,会导致中断被吞,上层逻辑失去响应能力 - 误以为
unpark()必须在park()之前调用 —— 实际可以乱序,但一旦park()返回,permit 就归零,后续unpark()才有效 - 在非 volatile 字段上做 park 前的状态检查(比如 while (!ready) LockSupport.park())—— 缺少内存屏障,可能导致线程永远看不到 ready 变化,必须配合 volatile 读或
Unsafe.loadFence()
复杂点在于:permit 状态不可见、不可调试、不报错,出问题时往往表现为“某线程突然不醒了”,而不是抛异常。查这类问题,优先看 unpark 是否真被执行、目标线程是否还存活、以及 park 前后的内存可见性保障是否到位。










