以下是关于Java中ReentrantReadWriteLock的全面解析,结合其设计原理、使用方式、适用场景及注意事项:
🔧 一、基本概念与核心特性
1. 读写锁定义
- 读锁(ReadLock):允许多个线程同时持有,用于并发读取共享资源(读读共享)。
- 写锁(WriteLock):独占锁,同一时刻仅允许一个线程持有,写入时阻塞所有读写操作(读写互斥、写写互斥)。
2. 核心特性
- 重入性:同一线程可重复获取同一把读锁或写锁;写锁可降级为读锁(先持写锁→再持读锁→释放写锁),但读锁不可升级为写锁(易死锁)。
- 公平性选择:构造时可选公平模式(
new ReentrantReadWriteLock(true))或非公平模式(默认)。公平锁按FIFO分配,避免饥饿;非公平锁吞吐量更高。 - 锁降级:写锁持有期间可获取读锁,再释放写锁,保证数据修改后其他线程无法立即修改(避免脏读)。
💻 二、使用方式与代码示例
1. 基础用法
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class CacheSystem {
private final Map<String, String> cache = new HashMap<>();
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
// 读操作
public String get(String key) {
readLock.lock();
try {
return cache.get(key);
} finally {
readLock.unlock();
}
}
// 写操作
public void put(String key, String value) {
writeLock.lock();
try {
cache.put(key, value);
} finally {
writeLock.unlock();
}
}
}
2. 锁降级示例
public void processData() {
readLock.lock();
if (!dataValid) { // 检查数据失效
readLock.unlock(); // 必须先释放读锁
writeLock.lock(); // 获取写锁
try {
if (!dataValid) { // 二次检查(避免重复更新)
updateData(); // 更新数据
dataValid = true;
}
readLock.lock(); // 写锁未释放时获取读锁(降级关键)
} finally {
writeLock.unlock(); // 释放写锁,保留读锁
}
}
try {
useData(); // 使用数据(仍持有读锁)
} finally {
readLock.unlock();
}
}
关键点:锁降级确保数据更新后,其他线程无法修改数据,但可并发读取。
⚙️ 三、内部实现原理
1. AQS 状态管理
- 使用一个32位
int(state)表示锁状态:- 高16位:读锁持有次数(如
state >>> 16)。 - 低16位:写锁重入次数。
- 高16位:读锁持有次数(如
- 写锁获取:检查
state是否为0(无锁)或当前线程已持有写锁(可重入)。 - 读锁获取:检查低16位(写锁状态),若为0则通过CAS增加高16位。
2. 互斥控制
- 写锁阻塞条件:当读锁被持有时,写锁请求需等待所有读锁释放。
- 读锁阻塞条件:当写锁被持有时,新读锁请求需等待写锁释放。
⚖️ 四、优缺点分析
| 维度 | 优点 | 缺点 |
|---|---|---|
| 并发性能 | 读多写少场景下吞吐量高(无锁读并发) | 写操作频繁时性能差(写锁阻塞所有读写) |
| 数据一致性 | 写锁独占保证强一致性 | 读操作可能读到旧数据(弱一致性) |
| 功能灵活性 | 支持锁降级、重入、公平性选择 | 不支持锁升级(读锁→写锁会死锁) |
| 资源开销 | 读锁无竞争时开销低 | 状态维护复杂(AQS队列+CAS操作) |
🎯 五、适用场景与替代方案
1. 理想场景
- 读多写少:如缓存系统(90%读+10%写)。
- 配置管理:全局配置高频读取,低频更新。
- 数据快照:需确保写入后不被立即覆盖(锁降级)。
2. 不适用场景
- 写操作频繁:写锁导致线程频繁阻塞,性能低于
ReentrantLock。 - 强一致性需求:读锁不保证实时性,需改用
StampedLock(乐观读)。
3. 替代方案对比
| 场景 | 推荐方案 |
|---|---|
| 读写均衡+低延迟 | StampedLock(乐观读模式) |
| 简单互斥 | ReentrantLock 或 synchronized |
| 高并发集合 | ConcurrentHashMap |
💎 总结
- 核心价值:通过读写分离最大化读并发性能,特别适合读多写少场景(如缓存、配置管理)。
- 最佳实践:
- 优先用非公平锁(默认)提升吞吐量。
- 锁降级用于确保数据修改后的安全读取。
- 避免锁升级(先读后写需先释放读锁)。
- 监控锁竞争情况(
getQueueLength())调整设计。
- 避坑指南:写操作应快速完成,避免长时间阻塞读操作;复杂同步逻辑可结合
Condition(仅写锁支持)。
通过合理应用
ReentrantReadWriteLock,可在保证线程安全的前提下显著提升系统并发能力 🔥。建议结合源码(如Sync类)深入理解AQS的实现机制。
198

被折叠的 条评论
为什么被折叠?



