ReenTrantLock可重入的互斥锁
约 982 字大约 3 分钟
2025-10-13
将从 ReentrantLock 的功能作用、解决的问题、使用方式、实现原理来介绍。
1. 功能作用
ReentrantLock 是 Java 提供的一个可重入的互斥锁,它提供了与 synchronized 关键字类似的同步功能,但是拥有更强大的灵活性和高级特性。
核心功能包括:
- 互斥访问:保证同一时间只有一个线程能持有锁。
- 可重入性:同一个线程可以多次获取同一把锁而不会被自己阻塞。
- 尝试非阻塞获取锁:
tryLock()方法可以尝试获取锁,如果失败不会阻塞线程。 - 可中断的锁等待:
lockInterruptibly()方法允许在等待锁的过程中响应中断。 - 公平锁与非公平锁:可以选择创建公平锁(先来先服务)或非公平锁(默认,允许插队)。
2. 解决了什么问题
它解决了 synchronized 关键字在一些场景下的局限性:
- 无法中断的等待:使用
synchronized时,若线程无法获取锁,会一直阻塞,无法被中断。 - 非阻塞地获取锁:
synchronized无法尝试获取锁,要么成功,要么无限等待。 - 不公平的锁竞争:
synchronized的锁是非公平的,可能导致某些线程长时间饥饿。 - 单一的等待条件:一个
synchronized锁只关联一个等待队列(通过wait/notify),无法实现多条件通知。
ReentrantLock 提供了更细粒度的锁控制,让开发者能构建更复杂的同步结构。
3. 使用方式
标准使用模板:
import java.util.concurrent.locks.ReentrantLock;
public class Counter {
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock(); // 获取锁
try {
count++;
// 其他需要同步的操作...
} finally {
lock.unlock(); // 必须在finally块中释放锁!
}
}
// 使用tryLock的示例
public boolean tryIncrement() {
if (lock.tryLock()) { // 尝试获取锁,立即返回
try {
count++;
return true;
} finally {
lock.unlock();
}
} else {
// 获取锁失败,执行其他逻辑
return false;
}
}
}4. 实现原理分析
ReentrantLock 的底层实现依赖于三个核心组件:
a) AQS
ReentrantLock 的所有同步功能都委托给了一个内部类 Sync,而 Sync 继承自 AbstractQueuedSynchronizer。AQS 是 JUC 包的核心,它提供了一个用于构建锁和同步器的框架。
b) 状态变量
AQS 内部维护了一个 volatile 的 state 变量:
state == 0:锁未被任何线程持有。state >= 1:锁已被线程持有。数值代表持有线程的重入次数。
c) CLH队列的变体
AQS 通过一个 FIFO 的等待队列来管理所有等待获取锁的线程。
工作流程:
加锁 (lock()):
- 通过 CAS 操作尝试将
state从 0 改为 1。 - 如果成功,则设置当前线程为独占锁的持有者。
- 如果失败(锁已被占用),则检查持有者是否是当前线程(重入),如果是则
state + 1。 - 如果都不是,则将当前线程封装成节点加入等待队列并挂起。
解锁 (unlock()):
- 将
state减 1(对于重入,可能只是减少计数)。 - 如果
state变为 0,则完全释放锁,清空独占线程。 - 唤醒等待队列中下一个符合条件的线程去尝试获取锁。
公平 vs 非公平:
- 非公平锁:线程在尝试获取锁时,不管等待队列中是否有其他线程,直接尝试 CAS 抢锁。性能高,但可能导致饥饿。
- 公平锁:线程在尝试获取锁时,如果等待队列不为空,则自觉排队。保证了公平性,但性能相对较低。
总结
ReentrantLock 是一个功能丰富的互斥锁,通过 AQS 框架、CAS 操作 和 等待队列 实现了比 synchronized 更灵活的锁机制。它是解决复杂并发问题的强大工具,但在简单场景下,synchronized 因其简洁性仍是首选。
