可中断和不可中断锁
# 可中断锁与不可中断锁
在 Java 并发编程中,可中断锁与不可中断锁的核心区别在于:线程在获取锁而被阻塞时,是否能够响应外部的中断信号并提前放弃等待。
1. 不可中断锁
当线程因为获取不到锁而进入阻塞状态后,即使其他线程调用了该线程的 interrupt() 方法,它也不会退出阻塞或抛出异常,而是会一直阻塞直到成功获取到锁。不过,中断信号会被保留,待线程获取锁后,可以通过检查中断状态来进行后续处理。
- 代表实现:
synchronized关键字、ReentrantLock的lock()方法。
2. 可中断锁
当线程因为获取不到锁而阻塞时,它能够响应中断。如果在线程等待锁的过程中被中断,它会立即停止等待,并抛出 InterruptedException 异常。这为开发者提供了一种机制,可以在等待资源超时或被通知取消时,主动释放线程资源,避免线程永久阻塞。
- 代表实现:
ReentrantLock的lockInterruptibly()方法、tryLock(long timeout, TimeUnit unit)方法。
总结: synchronized 由于是不可中断的,在死锁场景下可能会造成线程无限期阻塞;而 ReentrantLock 提供了可中断的锁获取方式,使得在复杂并发场景下,我们可以更灵活地控制线程的生命周期和资源争抢,提高系统的健壮性。
场景:
“比如在一个批量任务处理系统中,如果用户取消了任务,我们可以通过中断正在等待锁的工作线程,让它立即释放资源并响应取消,而不是一直阻塞在那里占用系统资源。”
# lock() vs lockInterruptibly() 对比
| 方法 | 是否可中断 | 使用场景 |
|---|---|---|
lock() | 不可中断 | 确定必须拿到锁的场景 |
lockInterruptibly() | 可中断 | 任务可取消、有超时控制的场景 |
# 场景设计
两个线程(t1 和 t2)争抢同一把锁:
- t1 先拿到锁,然后持有锁 5 秒钟(模拟长时间业务处理)
- t2 尝试获取锁,但因为 t1 持有锁,t2 进入阻塞等待
- 主线程在 2 秒后中断 t2
- 观察 t2 对中断的响应
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;
public class LockInterruptiblyExample {
private static final ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
// 线程1:先获取锁并长时间持有
Thread t1 = new Thread(() -> {
lock.lock();
try {
System.out.println("线程1: 拿到锁,开始处理业务,将持续5秒");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
System.out.println("线程1: 休眠被中断,但锁还在我手里");
}
System.out.println("线程1: 业务处理完毕,释放锁");
} finally {
lock.unlock();
}
}, "线程1");
// 线程2:使用 lockInterruptibly() 获取锁,可以被中断
Thread t2 = new Thread(() -> {
try {
System.out.println("线程2: 尝试通过 lockInterruptibly() 获取锁...");
// 关键点:这里使用可中断的获取锁方式
lock.lockInterruptibly();
try {
System.out.println("线程2: 成功拿到锁!(这条消息可能看不到,因为会被中断)");
} finally {
lock.unlock();
System.out.println("线程2: 释放锁");
}
} catch (InterruptedException e) {
System.out.println("线程2: 在等待锁的过程中被中断了!中断异常捕获");
System.out.println("线程2: 执行清理工作或取消操作...");
// 这里可以做资源清理、状态恢复等操作
}
}, "线程2");
// 启动线程
t1.start();
// 稍微等待一下,确保线程1先拿到锁
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
// 等待2秒后中断线程2
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程: 尝试中断线程2");
t2.interrupt();
}
}
# 运行结果
线程1: 拿到锁,开始处理业务,将持续5秒
线程2: 尝试通过 lockInterruptibly() 获取锁...
主线程: 尝试中断线程2
线程2: 在等待锁的过程中被中断了!中断异常捕获
线程2: 执行清理工作或取消操作...
线程1: 业务处理完毕,释放锁
上次更新: 2026-03-20 10:18:13