Java中的ReentrantLock与Condition详解
字数 1632 2025-11-20 00:55:52
Java中的ReentrantLock与Condition详解
1. 背景与问题描述
在多线程编程中,synchronized是Java原生的互斥同步机制,但它的功能相对简单(例如无法实现公平锁、等待条件单一)。ReentrantLock是JUC(java.util.concurrent)包下提供的显式锁,支持更灵活的锁操作,例如可中断获取锁、超时获取锁、公平锁等。而Condition则是与ReentrantLock配合使用的等待/通知机制,类似于Object.wait()/notify(),但支持多个等待条件队列。
核心问题:
ReentrantLock如何实现可重入性?- 与
synchronized相比有哪些优势? Condition的工作原理是什么?如何实现多条件等待?
2. ReentrantLock的基本特性
(1)可重入性
- 定义:同一个线程可以重复获取同一把锁,而不会阻塞自己。
- 实现原理:
ReentrantLock内部通过AQS(AbstractQueuedSynchronizer)的state字段记录重入次数。- 线程首次获取锁时,
state从0变为1,并记录锁的持有者(当前线程)。 - 同一线程再次获取锁时,
state递增;释放锁时state递减,直到state=0时完全释放锁。
(2)公平锁 vs 非公平锁
- 公平锁:线程按申请锁的顺序排队获取锁。
- 非公平锁:允许插队,可能造成线程饥饿但吞吐量更高。
- 示例:
ReentrantLock fairLock = new ReentrantLock(true); // 公平锁 ReentrantLock nonfairLock = new ReentrantLock(); // 非公平锁(默认)
(3)灵活的方法
lock():获取锁,阻塞直到成功。tryLock():尝试非阻塞获取锁,立即返回是否成功。lockInterruptibly():可响应中断的获取锁方式。unlock():释放锁(必须成对调用,通常在finally块中释放)。
3. Condition的等待/通知机制
(1)与Object监视器方法的对比
synchronized中,一个锁对象只能有一个等待队列(通过wait()/notify()操作)。ReentrantLock可以绑定多个Condition,每个Condition维护独立的等待队列。
(2)核心方法
await():释放锁并进入等待状态(类比wait())。signal():唤醒一个等待线程(类比notify())。signalAll():唤醒所有等待线程(类比notifyAll())。
(3)典型应用场景:生产者-消费者模型
ReentrantLock lock = new ReentrantLock();
Condition notFull = lock.newCondition(); // 条件1:队列未满
Condition notEmpty = lock.newCondition(); // 条件2:队列非空
// 生产者
public void put(Object item) throws InterruptedException {
lock.lock();
try {
while (queue.isFull()) {
notFull.await(); // 等待"未满"条件
}
queue.add(item);
notEmpty.signal(); // 唤醒消费者
} finally {
lock.unlock();
}
}
// 消费者
public Object take() throws InterruptedException {
lock.lock();
try {
while (queue.isEmpty()) {
notEmpty.await(); // 等待"非空"条件
}
Object item = queue.remove();
notFull.signal(); // 唤醒生产者
return item;
} finally {
lock.unlock();
}
}
4. 底层原理:AQS与条件队列
(1)AQS的同步队列
ReentrantLock的锁竞争通过AQS的同步队列(CLH队列)管理。- 线程获取锁失败时,会被封装成Node节点加入队列尾部,通过自旋或阻塞等待唤醒。
(2)Condition的条件队列
- 每个
Condition对象维护一个独立的条件队列(单向链表)。 - 调用
await()时,线程会释放锁,并将自身从AQS同步队列移到Condition的条件队列中等待。 - 调用
signal()时,将条件队列中的头节点移回AQS同步队列,参与锁竞争。
(3)流程示意图
同步队列(AQS): [ThreadA] <- [ThreadB] <- [ThreadC]
条件队列(Condition): [ThreadX] <- [ThreadY]
ThreadX调用await():
1. ThreadX从同步队列移出,加入条件队列。
2. ThreadB成为同步队列头节点,获取锁。
ThreadB调用signal():
1. 将ThreadX从条件队列移回同步队列尾部。
2. ThreadX在同步队列中等待锁。
5. 对比synchronized的优劣
优势:
- 功能丰富:支持公平锁、可中断、超时尝试等。
- 性能优化:在竞争激烈时,非公平锁的吞吐量更高。
- 多条件等待:通过多个
Condition精确控制线程唤醒。
劣势:
- 需手动释放锁:忘记调用
unlock()会导致死锁。 - 代码复杂:需配合
try-finally块确保锁释放。
6. 总结
ReentrantLock通过AQS实现可重入锁,支持公平/非公平策略。Condition将等待/通知机制解耦,允许一个锁绑定多个条件队列。- 适用场景:需要高级功能(如公平性、多条件)的并发控制,但需注意手动管理锁的释放。