synchronized 无法实现自旋+阻塞组合,因其是重量级锁且无尝试获取、提前唤醒接口;而 LockSupport 的 park/unpark 提供底层控制能力,支持自旋判断、精确挂起与唤醒。

为什么不能直接用 synchronized 实现自旋+阻塞组合
synchronized 是 JVM 内置的重量级锁,进入阻塞态后无法干预线程状态,也不能在阻塞前插入自旋逻辑;它没有暴露“尝试获取”或“提前唤醒”的接口。而 LockSupport 的 park/unpark 是底层原语,允许你在任意位置决定是否自旋、何时挂起、由谁唤醒——这才是组合策略的基础。
LockSupport.park() 前必须做自旋判断,但不能空转太久
自旋本质是用 CPU 换等待延迟,适合临界区极短、竞争不激烈的情况。但盲目自旋会浪费资源,尤其在多核争抢下可能引发缓存乒乓(cache bouncing)。
- 建议用固定次数(如 100–500 次)或带退避的自旋:每次失败后
Thread.onSpinWait()+ 短暂Thread.yield() - 自旋期间必须读取共享状态变量(如锁标志位),且该变量需声明为
volatile或用Unsafe.getIntVolatile(),否则可能永远看不到更新 - 一旦自旋超限,立刻调用
LockSupport.park()进入 WAITING 状态,避免持续占用 CPU
唤醒必须用 LockSupport.unpark(thread),且要确保目标线程确实被 park 住了
常见错误是:在目标线程还没执行到 park() 就调用 unpark() ——这没问题,因为 permit 会保留;但更危险的是:目标线程已因其他原因退出、中断或根本没启动,此时 unpark() 无效且无提示。
- 务必在调用
unpark()前检查线程状态:if (thread != null && thread.isAlive()) - 不要依赖
thread.getState() == Thread.State.WAITING来判断是否可唤醒——park()后线程可能刚进入 WAITING 就被调度器切走,状态检查和unpark()之间存在竞态 - 推荐做法:用一个
volatile boolean标志配合自旋,让等待线程主动“登记”自己已准备好 park,再由持有锁者确认后 unpark
permit 机制让顺序不再敏感,但容易掩盖逻辑漏洞
unpark() 先于 park() 调用不会丢信号,这是它比 Object.wait() 强的核心点。但这也意味着:如果某次 unpark() 被误发,而后续本该发生的 park() 因条件未满足跳过了,那个 permit 就成了“幽灵许可”,可能导致下一次不该唤醒的 park() 瞬间返回,破坏同步契约。
- 每个锁实例应维护独立的许可生命周期,避免跨操作复用同一个线程对象反复
unpark/park - 在 park 前清空旧 permit:可先调用一次
LockSupport.parkNanos(1)(立即超时)消耗掉残留 permit,再正式 park - 调试时善用
LockSupport.park(Object blocker),传入当前锁对象,这样 jstack 能看到阻塞上下文,比如"waiting on java.util.concurrent.locks.ReentrantLock$NonfairSync@123456"
实际中最容易被忽略的,是 permit 的“一次性”和不可重入性:它不像 ReentrantLock 那样记录持有者和重入次数,也不保证唤醒顺序。你得自己用状态机管理线程排队、唤醒优先级、以及公平性策略——这些逻辑一旦出错,问题往往在线程调度边界上,极难复现。










