locksupport.park 本质是阻塞式等待,不能用于构建非阻塞同步组件;非阻塞必须全程用户态自旋(如 cas + onspinwait),park 会导致线程挂起、上下文切换,且不响应中断、无唤醒原因反馈,无法满足退避重试的动态精度与协作需求。

不能用 LockSupport.park 构建“非阻塞”同步组件——它本质就是阻塞的。所谓“非阻塞同步”,必须全程在用户态自旋(如 CAS + Thread.onSpinWait()),不交出 CPU 调度权。而 LockSupport.park() 会让线程进入 WAITING 或 TIMED_WAITING 状态,JVM 直接挂起线程、退出运行队列,这属于典型的阻塞式等待。如果你看到“非阻塞 + park”的提法,基本是术语误用。
为什么退避重试不能只靠 park/unpark
退避重试(backoff retry)的核心诉求是:条件未满足时,先短时自旋降低延迟,再逐步延长休眠以节省资源。但 park() 没有“短时”概念——哪怕只 park 1 纳秒,线程也必然被调度器摘出 CPU,带来上下文切换开销;而真正的退避需要在“不切换”的前提下动态调整等待强度。
-
park()不响应中断,也不检查中断状态,无法优雅退出重试循环 - 连续
unpark()不叠加 permit,但退避逻辑常需多次唤醒信号协同,park/unpark 的二元语义反而增加状态管理复杂度 - park 无返回值,无法区分是超时退出、被 unpark 唤醒,还是被中断——这对重试策略决策是致命缺失
- 在 synchronized 或 ReentrantLock 持有锁期间调用
park(),锁不会释放,极易造成死锁或锁持有时间不可控
真正可行的退避重试组合:自旋 + onSpinWait + parkNanos(混合模式)
混合模式是 AQS 等成熟同步器的实际做法:前几轮用纯用户态自旋,避免调度开销;检测到竞争加剧或等待时间变长后,才降级为 park。关键不是“能不能用 park”,而是“什么时候、以什么精度、带什么防护地切入 park”。
- 自旋阶段建议用
Thread.onSpinWait()(Java 9+),它向 CPU 发出提示,可能触发节能指令(如 x86 的 PAUSE),比空循环更友好 - 自旋轮数不宜固定,应结合当前系统负载或重试次数动态计算,例如:
int spins = Math.min(1 - 转入
parkNanos()前,必须确保已通过 volatile 变量或 CAS 检查过最新状态,防止虚假唤醒导致跳过条件判断 -
parkNanos()必须传入 blocker 参数(如this),否则jstack中无法识别阻塞点,线上问题几乎无法定位 - 不要用
TimeUnit.NANOSECONDS.toNanos(1)这类极小值——JVM 和 OS 对纳秒级休眠无实际保障,实测往往等效于 park(),且徒增调用开销
一个可落地的退避循环骨架(Java)
以下不是完整同步器,而是退避逻辑内核,可嵌入 AQS 子类或自定义同步点:
long spinTimeoutNs = TimeUnit.MICROSECONDS.toNanos(50);
int maxSpins = 16;
int spins = 0;
<p>while (!isAcquired()) {
if (spins </p><pre class="brush:java;toolbar:false;">// 退避阶段:转入 parkNanos,但必须包裹在条件检查中
if (!isAcquired()) {
LockSupport.parkNanos(this, spinTimeoutNs);
// park 返回后必须重新检查,不能假设条件已满足
spinTimeoutNs = Math.min(spinTimeoutNs * 2, TimeUnit.MILLISECONDS.toNanos(1));
}}
注意:isAcquired() 必须是 volatile 读或 CAS 检查,不能依赖本地缓存;spinTimeoutNs 每次翻倍是典型指数退避,但上限要设防,避免单次 park 过长影响响应性。
真正容易被忽略的点是:**parkNanos 的 timeout 参数不是精度承诺,而是上界**。JVM 不保证唤醒时间误差在多少纳秒内,Linux 下通常受 timer tick(如 1–15ms)和调度延迟影响。如果你需要微秒级确定性,得用 busy-wait + System.nanoTime() 手动轮询,但代价是 100% 占用一个 CPU 核——这正是“高性能”和“低资源消耗”之间的根本权衡,没有银弹。










