ReentrantLock 详解
一、核心特性
ReentrantLock 是 Java 并发包(java.util.concurrent)中提供的显式可重入锁,相比 synchronized,其优势在于更灵活的锁控制和更丰富的功能:
- 可重入性:同一线程可多次获取锁。
- 公平性选择:支持公平锁和非公平锁(默认非公平锁)。
- 可中断锁:线程等待锁时可响应中断。
- 超时机制:支持尝试获取锁(
tryLock)。 - 多条件变量:通过
Condition实现多条件等待队列。
二、实现原理
ReentrantLock 基于 AQS(AbstractQueuedSynchronizer) 实现,核心依赖以下机制:
1. AQS 的核心组件
• 状态变量(state) : • 表示锁的持有次数。 • 当 state = 0 时,锁未被占用;state > 0 时,锁被占用,数值表示重入次数。 • CLH 队列: • 一个虚拟的双向队列,存储等待锁的线程。 • 通过 Node 节点封装线程和状态(如等待状态)。
2. 锁的获取与释放
• 非公平锁(默认) :
final void lock() {
if (compareAndSetState(0, 1)) // 尝试直接获取锁
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1); // 进入队列等待
}
• 流程: 1. 直接尝试通过 CAS 获取锁。 2. 若失败,调用 acquire() 进入队列等待。
• 公平锁:
final void lock() {
acquire(1); // 直接进入队列等待
}
• 流程: 1. 检查队列中是否有等待线程。 2. 若无,尝试 CAS 获取锁;若有,排队等待。
3. 可重入性实现
• 获取锁: 当前线程与持有锁的线程相同时,state 递增。
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires; // 重入时 state 递增
setState(nextc);
return true;
}
return false;
}
• 释放锁: 每次释放锁时 state 递减,直至 state = 0 时完全释放。
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
三、核心功能与使用场景
1. 公平锁与非公平锁
• 非公平锁(默认) : • 特点:允许线程插队,吞吐量高。 • 适用场景:高并发且对响应时间不敏感的场景(如缓存系统)。
• 公平锁: • 特点:按队列顺序获取锁,避免饥饿。 • 适用场景:对公平性要求高的场景(如交易系统)。
示例:
// 创建公平锁
ReentrantLock fairLock = new ReentrantLock(true);
// 创建非公平锁(默认)
ReentrantLock nonfairLock = new ReentrantLock();
2. 可中断锁
• 方法:lockInterruptibly() • 场景:线程等待锁时需响应中断(如用户取消操作)。 示例:
ReentrantLock lock = new ReentrantLock();
try {
lock.lockInterruptibly(); // 可中断的锁获取
// 执行临界区代码
} catch (InterruptedException e) {
// 处理中断
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
3. 超时机制
• 方法:tryLock(long timeout, TimeUnit unit) • 场景:避免死锁或长时间等待。 示例:
if (lock.tryLock(3, TimeUnit.SECONDS)) {
try {
// 执行临界区代码
} finally {
lock.unlock();
}
} else {
// 处理超时
}
4. 多条件变量(Condition)
• 方法:newCondition() • 场景:多条件等待(如生产者-消费者模型)。 示例:
ReentrantLock lock = new ReentrantLock();
Condition notFull = lock.newCondition(); // 队列未满条件
Condition notEmpty = lock.newCondition(); // 队列非空条件
// 生产者
public void produce() {
lock.lock();
try {
while (queue.isFull()) {
notFull.await(); // 等待队列未满
}
queue.add(item);
notEmpty.signal(); // 通知消费者
} finally {
lock.unlock();
}
}
四、性能与优化
- 减少锁竞争: • 缩小锁的粒度(如分段锁)。 • 使用非公平锁提高吞吐量。
- 避免锁嵌套: • 在临界区内避免调用可能阻塞的方法(如 I/O 操作)。
- 优先使用无锁结构: • 结合
Atomic类或ConcurrentHashMap减少锁使用。
五、对比 synchronized
| 特性 | ReentrantLock | synchronized |
|---|---|---|
| 锁类型 | 显式锁(需手动释放) | 隐式锁(自动释放) |
| 公平性 | 支持公平锁和非公平锁 | 仅非公平锁 |
| 可中断性 | 支持(lockInterruptibly()) | 不支持 |
| 超时机制 | 支持(tryLock) | 不支持 |
| 多条件变量 | 支持(Condition) | 不支持(仅一个等待队列) |
| 性能 | 高竞争下更优 | 低竞争下更优 |
六、适用场景总结
- 需要公平锁:如订单处理系统,确保先到先服务。
- 需超时或中断控制:如避免死锁或用户取消操作。
- 多条件协作:如生产者-消费者模型中的多条件等待。
- 复杂锁逻辑:如尝试获取锁、锁分段等高级需求。
七、代码示例
public class Counter {
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}
八、注意事项
- 必须手动释放锁: 在
finally块中调用unlock(),避免锁泄漏。 - 避免重复解锁: 确保当前线程持有锁后再调用
unlock()。 - 锁的公平性选择: 公平锁可能降低吞吐量,仅在必要时使用。
通过合理使用 ReentrantLock,可以显著提升多线程程序的灵活性和性能,尤其适用于复杂的高并发场景。