Java中的ReentrantLock
是java.util.concurrent.locks
包下提供的一个可重入互斥锁,用于替代synchronized
关键字实现更灵活的线程同步。以下是其核心特性和使用方法的详细说明:
核心特性
- 可重入性
同一个线程可以重复获取同一个锁(锁的持有计数器递增),避免死锁。例如,递归调用或嵌套同步块时无需重复释放锁。 - 公平性选择
构造函数支持创建公平锁(fair = true
)或非公平锁(默认)。- 公平锁:按请求顺序分配锁,避免线程饥饿,但性能较低。
- 非公平锁:允许插队,提高吞吐量,但可能导致某些线程长期等待。
- 灵活的锁控制
支持tryLock()
(尝试非阻塞获取锁)、lockInterruptibly()
(可中断的锁请求)和超时机制,避免无限等待。 - 条件变量(Condition)
通过newCondition()
创建多个Condition
实例,实现线程间精确的等待/唤醒机制。
核心方法
方法 | 说明 |
---|---|
lock() | 获取锁,若锁被占用则阻塞等待。 |
unlock() | 释放锁,必须在finally 块中调用。 |
tryLock() | 尝试立即获取锁,成功返回true ,否则返回false 。 |
tryLock(timeout, unit) | 在指定时间内尝试获取锁。 |
lockInterruptibly() | 获取锁,但允许被其他线程中断。 |
isHeldByCurrentThread() | 检查当前线程是否持有锁。 |
代码示例
1. 基本用法
ReentrantLock lock = new ReentrantLock();public void safeMethod() {lock.lock();try {// 临界区代码} finally {lock.unlock();}
}
2. 尝试获取锁
if (lock.tryLock(1, TimeUnit.SECONDS)) {try {// 成功获取锁后的操作} finally {lock.unlock();}
} else {// 超时后的备选方案
}
3. 使用Condition实现生产者-消费者
ReentrantLock lock = new ReentrantLock();
Condition notEmpty = lock.newCondition();
Condition notFull = lock.newCondition();
Queue<Integer> queue = new LinkedList<>();
int capacity = 10;// 生产者
public void produce(int value) throws InterruptedException {lock.lock();try {while (queue.size() == capacity) {notFull.await(); // 等待队列非满}queue.add(value);notEmpty.signal(); // 通知消费者} finally {lock.unlock();}
}// 消费者
public int consume() throws InterruptedException {lock.lock();try {while (queue.isEmpty()) {notEmpty.await(); // 等待队列非空}int value = queue.remove();notFull.signal(); // 通知生产者return value;} finally {lock.unlock();}
}
与synchronized的对比
特性 | ReentrantLock | synchronized |
---|---|---|
锁获取方式 | 显式调用lock() 和unlock() | 隐式通过代码块或方法 |
可中断性 | 支持(lockInterruptibly() ) | 不支持 |
超时机制 | 支持tryLock(timeout) | 不支持 |
公平性 | 可配置公平或非公平锁 | 仅非公平锁 |
条件变量 | 支持多个Condition | 单一wait() /notify() |
适用场景
- 需要细粒度控制锁(如超时、可中断)。
- 需要公平性策略(如任务调度系统)。
- 需要多个条件变量(如生产者-消费者模型中的不同等待条件)。
- 需要尝试获取锁的非阻塞逻辑。
实现原理
ReentrantLock
的核心逻辑委托给内部类 Sync
,而 Sync
继承自 AQS。AQS 是一个用于构建锁和同步器的框架,通过 状态(state) 和 CLH 队列(Craig, Landin, and Hagersten 队列,一种 FIFO 双向队列)管理线程的竞争与调度。
- 状态(state):
AQS 的state
字段表示锁的持有次数(可重入性)。state = 0
:锁未被任何线程占用。state > 0
:锁被占用,值表示当前线程的重入次数。
- CLH 队列:
竞争锁失败的线程会被封装为Node
对象,加入 CLH 队列等待唤醒。队列通过CAS
操作保证线程安全。
是否是公平锁的区别在于:线程获取锁时是加入到 CLH 队列尾部还是直接利用 CAS 争抢锁。
注意事项
- 必须释放锁
确保在finally
块中调用unlock()
,防止死锁。 - 避免嵌套死锁
重入次数必须与释放次数匹配(例如,加锁两次需解锁两次)。 - 性能考量
在低争用情况下,synchronized
性能接近或优于ReentrantLock
;高争用时需测试选择。
通过合理使用ReentrantLock
,可以显著提升多线程程序的灵活性和可靠性,尤其在需要复杂同步逻辑的场景中表现突出。