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,可以显著提升多线程程序的灵活性和可靠性,尤其在需要复杂同步逻辑的场景中表现突出。