synchronized 无法实现自旋+阻塞组合因其策略黑盒、不可控;locksupport.park()前须先cas尝试、再有限自旋、检查状态与中断;unpark()需精准唤醒队首线程,避免惊群;自旋次数宜100–500,过高耗cpu,过低损性能。

为什么不能直接用 synchronized 实现自旋+阻塞组合
因为 synchronized 是 JVM 层硬编码的锁机制,没有暴露「尝试获取失败后是否自旋」或「何时转为阻塞」的控制点。它内部策略完全黑盒:可能自旋几轮就挂起,也可能直接进队列,开发者无法干预。而 LockSupport 提供了精确的线程状态控制能力——park() 和 unpark() 的调用时机、目标线程、是否带超时,全由你决定。
LockSupport.park() 前必须做哪些检查才能避免无意义阻塞
直接调用 park() 等于把线程扔进 WAITING 状态,之后只能靠 unpark() 或中断唤醒。在锁场景下,这会导致严重问题:
- 没检查共享状态(比如锁标志位)就 park,可能刚阻塞完别人就释放了锁,白白错过
- 没做自旋就 park,会丢失 CPU 缓存局部性,尤其在高争用短临界区场景下性能暴跌
- 没设置中断响应逻辑,
park()不抛异常,线程被中断后仍处于 WAITING,但Thread.interrupted()已清零,容易漏处理
正确做法是:先用 CAS 尝试获取锁;失败后进入有限次自旋(比如 100–500 次),期间不断读取锁状态;自旋耗尽仍未获取成功,再调用 LockSupport.park(this) 阻塞自己。
如何用 LockSupport.unpark() 精确唤醒等待者而非广播
标准 Object.notifyAll() 或 Condition.signalAll() 会唤醒所有等待线程,造成惊群效应。而 LockSupport.unpark(Thread) 只唤醒指定线程,这是构建公平/非公平组合锁的关键。
实操要点:
- 维护一个等待线程队列(比如
AtomicReferenceFieldUpdater+ 自定义 Node 链表),每次park()前把自己入队,unpark()时只唤醒队首 - 不要依赖
Thread.currentThread()以外的线程引用——如果等待线程已退出或被 GC,unpark()无效但不报错,需配合状态标记(如 volatile int state)判断是否仍需唤醒 - 释放锁时,先更新共享状态(如 CAS 设置 unlocked),再
unpark();顺序颠倒可能导致唤醒后立即又抢锁失败,重新 park
自旋次数和 park 超时怎么设才不容易翻车
这两个参数没有银弹值,但有明确的踩坑边界:
- 自旋次数设太高(如 >2000),在单核或高负载机器上会显著抬升延迟,且浪费 CPU;设太低(如
-
parkNanos(long)的纳秒值别硬写常量——JVM 对极小超时(如 - 永远不要只用
parkUntil(long)而不校验状态:系统时钟可能被 NTP 调整,deadline 突然跳变会导致线程提前或永久阻塞 - 真实业务中,更稳妥的是「自旋 + parkNanos + 状态重检」三段式:自旋失败 → parkNanos(100_000) → 唤醒后立刻 CAS 检查锁,失败则重试循环
最易被忽略的一点:park() 不保证内存可见性自动刷新,必须在 park 前插入 volatile 读(如读一个 volatile boolean lockFree),否则可能因 CPU 缓存未更新而错过唤醒信号。










