理解 ReentrantLock 的核心特性与工作原理
掌握其基本使用方法及关键 API
能够正确实现可重入、公平性、中断响应等高级功能
明确 ReentrantLock 相较于 synchronized 的优势与适用场景
ReentrantLock 是 Java 并发包(java.util.concurrent.locks)中提供的一个可重入互斥锁,它实现了 Lock 接口,为开发者提供了比 synchronized 关键字更灵活、更强大的线程同步机制。所谓“可重入”(Reentrant),是指同一个线程可以多次获取同一把锁而不会导致死锁。每次 lock() 调用都会增加持有计数,必须通过相同次数的 unlock() 才能真正释放锁。
import java.util.concurrent.locks.ReentrantLock;
/**
* 共享资源类,使用 ReentrantLock 保证线程安全
*/
public class SharedResource {
private final ReentrantLock lock = new ReentrantLock();
/**
* 执行受保护的任务(临界区操作)
*/
public void performTask() {
lock.lock(); // 获取锁:若已被其他线程持有,则当前线程阻塞等待
try {
// 临界区:同一时间只允许一个线程执行此段代码
System.out.println(Thread.currentThread().getName() + " 正在执行任务");
// 模拟实际工作(可选)
// Thread.sleep(500);
} finally {
lock.unlock(); // 必须在 finally 块中释放锁,防止异常导致死锁
}
}
}重要原则:
unlock()必须放在finally块中,确保即使发生异常也能释放锁,防止死锁。
以下示例展示了同一个线程如何安全地多次获取同一把锁:
import java.util.concurrent.locks.ReentrantLock;
/**
* 演示 ReentrantLock 的可重入特性:
* 同一个线程可以多次获取同一把锁,而不会发生死锁。
*/
public class ReentrantDemo {
private final ReentrantLock lock = new ReentrantLock();
public void methodA() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 进入方法 A");
methodB(); // 同一线程再次请求同一把锁(可重入)
} finally {
lock.unlock();
}
}
public void methodB() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 进入方法 B");
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ReentrantDemo demo = new ReentrantDemo();
// 在主线程中调用 methodA,触发可重入行为
demo.methodA();
}
}输出:
main 进入方法 A
main 进入方法 B说明:
主线程在 methodA() 中已持有锁,调用 methodB() 时再次请求同一把锁。由于 ReentrantLock 支持可重入,该操作成功,持有计数变为 2。只有当两次 unlock() 都执行后,锁才被完全释放。
ReentrantLock 支持两种调度策略:
非公平锁(默认):允许插队,新来的线程可能比等待队列中的线程先获得锁,吞吐量更高。
公平锁:严格按照请求顺序分配锁(FIFO),避免线程饥饿,但性能略低。
// 创建公平锁
ReentrantLock fairLock = new ReentrantLock(true);
// 创建非公平锁(默认)
ReentrantLock unfairLock = new ReentrantLock(); // 等价于 new ReentrantLock(false);建议:除非有明确的公平性需求(如任务调度系统),否则优先使用默认的非公平锁以获得更好性能。
示例:使用 tryLock 避免长时间阻塞
if (lock.tryLock()) {
try {
// 执行临界区操作
} finally {
lock.unlock();
}
} else {
// 锁已被占用,执行备选逻辑(如记录日志、重试或跳过)
System.out.println("Failed to acquire lock, skipping operation.");
}优先考虑 synchronized:对于简单同步需求,synchronized 更简洁、安全,且 JVM 对其有深度优化。
仅在需要高级功能时使用 ReentrantLock:如超时获取、可中断、公平性或多条件等待。
始终在 finally 块中释放锁:防止因异常导致锁未释放,引发死锁。
避免嵌套锁顺序不一致:若需获取多把锁,应固定获取顺序以防止死锁。
慎用公平锁:除非业务逻辑严格要求 FIFO,否则非公平锁性能更优。
ReentrantLock 是一个功能强大、可重入的显式锁,提供比 synchronized 更精细的控制能力。
其核心优势在于可中断、可超时、可尝试、可公平的锁获取机制。
可重入特性允许同一线程多次持有锁,适用于递归调用或方法链场景。
尽管功能丰富,但使用不当(如忘记 unlock())易引发死锁,需格外谨慎。
在现代 Java 开发中,ReentrantLock 是构建高性能、高可靠并发系统的重要工具之一。
如果一个线程在持有 ReentrantLock 期间被中断,会发生什么?lockInterruptibly() 与普通 lock() 在中断处理上有何本质区别?
在什么场景下你会选择使用公平锁?请结合实际系统(如任务队列、资源分配器)举例说明。
设计一个银行转账系统,使用 ReentrantLock 实现账户间的线程安全转账,并说明如何避免死锁(提示:按账户 ID 排序获取锁)。