tryAcquireNanos通过动态重算剩余时间(nanosTimeout减去已耗时)控制超时,每次park唤醒后立即重试acquire并检查中断与条件;剩余时间≤1ms时转为自旋以避免park开销。

tryAcquireNanos 是怎么算超时时间的
它不是简单地等 nanosTimeout 纳秒,而是用「相对剩余时间」反复重算:每次被唤醒后,用原始超时值减去已耗时,得到新的 nanosTimeout。如果结果 ≤ 0,直接失败返回 false。
这意味着:线程可能在 park 前就被判定超时(比如刚进入方法就发现时间不够),也可能 park 中被中断或虚假唤醒,之后立刻重判剩余时间 —— 所以实际阻塞时长一定 ≤ 指定超时值,但通常略短。
- 别传入负数或 0,
tryAcquireNanos会立即返回false - 传入的
nanosTimeout必须是正数,且建议 ≥1000000(1ms),太小会导致精度丢失甚至直接跳过 park - 系统时钟漂移、GC 暂停会影响实际等待精度,不要指望微秒级准度
为什么 park 被唤醒后还要再 check 一次条件
AQS 的设计原则是「条件优先于时间」:只要在任意时刻成功 acquire 到 state,就该立刻返回 true,哪怕此时离超时还剩 99% 时间。反过来,如果条件不满足、时间又耗尽,才返回 false。
所以 tryAcquireNanos 的循环体里,每次从 LockSupport.parkNanos 返回后,第一件事就是调用子类实现的 tryAcquire 再试一次 —— 这是避免「唤醒即成功」逻辑漏掉的关键。
立即学习“Java免费学习笔记(深入)”;
- 虚假唤醒(spurious wakeup)真实存在,不重 check 会丢掉本可获取的锁
- 其他线程可能在你 park 期间 release 了资源,此时你一醒来就能拿走,没必要继续等
- 中断发生时,
parkNanos返回,但中断状态未清除,需由上层决定是否抛InterruptedException
自己写 tryAcquireNanos 时最容易错的三件事
如果你在自定义 AQS 同步器中重写 tryAcquireNanos,下面这些点几乎必踩坑:
- 没处理
Thread.interrupted(),导致中断被吞,上层收不到InterruptedException - 把
nanosTimeout直接传给parkNanos,而没减去前序耗时,造成总等待远超预期 - 在
tryAcquire成功后忘记调用setExclusiveOwnerThread(如果是独占模式),导致后续 reentrant 判定失效
正确写法核心骨架是:for (long nanosTimeout = ...; nanosTimeout > spinForTimeoutThreshold; ) { if (tryAcquire(arg)) return true; nanosTimeout = parkNanos(nanosTimeout) - 已耗时; } —— 注意那个减法不能少。
spinForTimeoutThreshold 这个阈值有什么用
当剩余超时时间极短(默认 1000_000L 纳秒,即 1ms)时,AQS 不再调用 parkNanos,而是进入自旋。因为 park/unpark 开销大,比不上空转几轮检查条件来得快。
这个阈值不可配置,硬编码在 AQS 里。它意味着:你设了个 500 微秒的超时,实际不会 park,而是在用户态循环检查;设成 2ms,则大概率会 park 一次,但剩余时间可能只剩几百微秒。
- 自旋期间 CPU 占用率会上升,慎用于高并发争抢场景
- 如果你的
tryAcquire实现很重(比如要查 DB 或 RPC),开启自旋反而更慢,此时应设更大超时避开该阈值 - 别试图反射修改
spinForTimeoutThreshold,它被final修饰,且逻辑深度耦合在循环判断中
超时控制的本质不是“精确停在某纳秒”,而是“不晚于那个时间点放弃”。真正难的从来不是计算,是区分清楚:此刻该响应中断?该让出 CPU?还是再拼一把?










