AQS 的 acquireInterruptibly 无法直接用于分布式锁,因其仅处理本地线程中断,不感知远程锁状态、网络超时或协调器释放,需同时实现本地等待控制与协调器主动清理。

为什么不能直接用 AQS 的 acquireInterruptibly 处理分布式锁
AQS 的 acquireInterruptibly 确实能响应线程中断,但它只作用于「本地线程阻塞等待」这一层。在分布式场景中,锁的持有者、等待者往往跨 JVM 甚至跨机器,AQS 根本不知道远程节点是否已宕机、网络是否超时、协调服务(如 Redis/ZooKeeper)是否已释放锁。一旦你把 ReentrantLock 拿去套分布式逻辑,acquireInterruptibly 就只是让本地线程早点从 park 状态醒来,对实际锁状态毫无影响——它既不通知远端释放锁,也不触发分布式协调器的清理动作。
真正可中断的分布式锁必须分两层控制
要让中断在分布式资源竞争中“生效”,得把中断信号同时透传到两个层面:
- 本地线程层:用
acquireInterruptibly或tryAcquireNanos避免无限挂起; - 分布式协调层:在中断发生时主动向协调器(如 Redis)发起解锁/取消请求,比如调用
DEL lock_key或 ZooKeeper 的delete,并确保该操作具备幂等性与最终一致性。
典型错误是只做第一层:线程被中断后退出了,但锁还在 Redis 里挂着,导致其他节点永远等不到它释放——这不是可中断,这是制造死锁。
tryAcquireNanos 在分布式锁中的真实用途不是“抢锁”,而是“设超时边界”
很多人误以为 tryAcquireNanos 能帮你自动完成分布式重试或清理,其实它只是 AQS 内部的一个带纳秒精度的自旋+park 控制逻辑。在封装分布式锁时,它的正确用法是:
- 作为本地等待策略的兜底:比如你封装了一个基于 Redis 的
RedisLock,其lock()方法内部调用tryAcquireNanos来控制“最多在本地循环等待多久”,而不是指望它连 Redis 都能超时; - 配合
Thread.interrupted()做快速退出分支:在每次尝试 SETNX 失败后检查中断状态,避免继续发无意义的网络请求; - 注意
nanosTimeout是本地计时,受系统时钟漂移和 GC 暂停影响,不能替代 Redis 的EXPIRE过期时间。
示例片段(伪代码):
if (Thread.interrupted()) throw new InterruptedException();
if (!redis.set(lockKey, requestId, "NX", "PX", expireMs)) {
if (nanosTimeout <= 0) return false;
// 这里才调用 AQS 的 tryAcquireNanos 做本地等待调度
// 它不会重试 Redis,只决定下一次重试前 sleep 多久
}
最容易被忽略的点:中断 ≠ 取消,而分布式环境下取消必须显式驱动
AQS 不提供 cancel 接口,interrupt() 只是设置中断标志位。在分布式锁中,这个标志位若不触发后续动作(如向 Redis 发送 EVAL 脚本尝试删除锁、向 ZooKeeper 删除 ephemeral node),就等于什么都没发生。更危险的是,有些 SDK(如 Lettuce)在连接断开时会静默吞掉中断,导致 acquireInterruptibly 表面返回,实际锁状态未更新。所以,所有基于 AQS 封装的分布式锁实现,都必须在 finally 块或 try-with-resources 的 close 中,强制执行一次「尽力而为」的锁清理,哪怕只是发个异步请求。

















