lockinterruptibly能响应中断而lock不能,因前者将中断作为退出条件立即抛异常,后者屏蔽中断直至获锁;需配合try-catch-finally确保unlock,且仅对未持锁线程有效。

lockInterruptibly 为什么能响应中断而 lock 不能
因为 lock() 是个“死磕型”操作:线程一旦开始抢锁,就彻底屏蔽中断信号,哪怕你调用 Thread.interrupt(),它也只默默记下中断状态,等拿到锁之后才抛 InterruptedException——但那时可能已经晚了。而 lockInterruptibly() 从一开始就把中断当作退出条件,只要被中断,立刻放弃排队、清理资源、抛出异常。
典型场景:后台任务执行中用户点了取消、HTTP 请求超时、Spring 的 @Async 方法需支持 cancel、定时任务被 shutdownNow 中断。
注意:lockInterruptibly() 只对「尚未获取到锁」的线程有效;如果线程已持有锁,再中断不会释放它——锁的释放永远只由 unlock() 显式触发。
必须配套 try-catch-finally 的写法
很多人以为加了 lockInterruptibly() 就万事大吉,结果一中断就漏掉 unlock(),造成死锁风险。它不像 synchronized 那样自动释放,必须手动兜底。
立即学习“Java免费学习笔记(深入)”;
正确写法只有这一种可靠模式:
Lock lock = new ReentrantLock();
try {
lock.lockInterruptibly(); // 可能抛 InterruptedException
// 执行临界区逻辑
} catch (InterruptedException e) {
// 中断发生:当前线程被外部要求停止,不做重试,直接退出
Thread.currentThread().interrupt(); // 恢复中断状态(供上层判断)
return; // 或 throw new RuntimeException(e);
} finally {
if (lock.isHeldByCurrentThread()) { // 防御性检查,避免 unlock 未持锁线程
lock.unlock();
}
}
常见错误:
- 把
lockInterruptibly()放在try外面 → 中断时根本进不到finally - catch 里没恢复中断状态 → 上层无法感知“这是被中断退出的”
- 忽略
isHeldByCurrentThread()→ 线程没拿到锁就被中断,unlock()会抛IllegalMonitorStateException
和 synchronized 对比:中断语义完全不同
synchronized 块完全不响应中断——哪怕你在等待 monitor entry 时被中断,JVM 也不会抛异常,中断标志只会被静默吞掉。所以想靠中断退出 synchronized 等待,根本做不到。
而 ReentrantLock.lockInterruptibly() 是目前 Java 标准库中唯一能真正实现“可中断等待”的原生机制(Condition.awaitNanos() 同理)。
性能差异可以忽略:两者底层都走 AQS,只是 lockInterruptibly() 多做一次中断检测,开销微乎其微。别为这点性能放弃可中断能力。
兼容性注意:Java 5+ 全支持,但 Android API Level lockInterruptibly()(需降级为轮询 + tryLock(timeout))。
中断后如何让上游知道“任务被取消”
单纯捕获 InterruptedException 并不等于业务层面完成了取消。下游调用方需要明确区分“执行失败”和“主动放弃”。
推荐做法:
- 方法签名声明 throws
InterruptedException,把中断责任向上传递 - 若封装成 CompletableFuture,用
completeExceptionally(new CancellationException())替代cancel(true),避免丢失中断上下文 - 在 Spring 中,配合
TaskExecutor使用时,确保线程池配置了setThreadFactory并保留中断传播链
最容易被忽略的一点:中断不是“立即终止线程”,而是发一个信号;线程是否响应、何时响应、响应后做什么,全由你自己代码控制。别指望调了 interrupt() 就万事大吉——锁释放了,但正在写的文件、发的 HTTP 请求、改的数据库,都得你手动收尾。










