locksupport 通过单许可模型实现线程唤醒:unpark 设置许可为1,park 消费许可,若已有许可则直接返回不阻塞;许可不累积、不丢失,避免了 wait/notify 的丢失唤醒问题。

先调用 unpark 再调用 park,线程确实不会阻塞——这不是巧合,而是 LockSupport 的核心设计机制决定的。它的关键在于“许可(permit)”模型,而非传统意义上的“等待-通知”配对。
许可机制是根本:每个线程自带一个可复用的开关
LockSupport 为每个线程维护一个隐式的许可状态,初始值为 0(不可用)。这个许可只有两种状态:0(无)或 1(有),且不累积、不排队、不丢失。
-
unpark(Thread t)的作用是:将目标线程的许可设为 1(如果已是 1,则无变化) -
park()的作用是:尝试消费一个许可;若当前许可为 1,则立即消费并返回,线程继续执行;若为 0,则挂起等待 - 因此,只要
unpark先执行,许可就已就位;后续park直接“取走”,不触发阻塞
与 wait/notify 的本质区别:无需同步块,也无竞态丢失风险
Object.wait() 要求必须在 synchronized 块中调用,且 notify() 若在 wait() 之前发生,该次唤醒就彻底丢失——这正是经典“丢失唤醒”问题的根源。
- LockSupport 没有这种依赖:
unpark和park可在任意上下文独立调用 - 许可状态由 JVM 级别的线程私有变量(如
Parker._counter)保证原子性,底层通过Unsafe.park/unpark实现 - 即使
unpark在线程启动前就执行,只要目标线程后续调用park,仍能立刻响应
为什么许可最多只有一个?这是为了简化语义和避免滥用
多次调用 unpark 并不会让许可变成 2 或更多,它始终被钳制在 1。这一设计带来两个实际好处:
- 避免因重复唤醒导致的逻辑错乱(比如本应只唤醒一次,却因多次 unpark 被误认为“可 park 多次”)
- 降低实现复杂度:JVM 只需维护单比特状态,配合轻量级自旋或系统级条件变量即可高效完成阻塞/唤醒
- 这也意味着:不能靠多次
unpark来“补偿”未处理的 park 请求;业务逻辑仍需自行确保语义正确性
实际编码中需注意的细节
虽然机制可靠,但使用时仍有几个关键点影响行为稳定性:
- 推荐使用带
Object blocker参数的park方法,便于线程 dump 时定位阻塞源头 -
park返回不等于“被 unpark 唤醒”,也可能因中断或虚假唤醒(spurious wakeup)返回,所以生产代码应始终配合循环检查条件 -
unpark对尚未启动或已终止的线程依然有效(许可状态仍被设置),但若线程永远不调用park,该许可就闲置,无副作用










