Java 读写锁ReentrantReadWriteLock
2025-03-20 本文已影响0人
饱饱抓住了灵感
一、读写锁核心概念
读写锁是一种并发控制机制,允许多个线程同时读取共享资源,但仅允许一个线程写入资源。适用于读多写少的高并发场景,可显著提升性能。
核心特性:
- 读锁(Read Lock):共享锁,多个线程可同时持有。
- 写锁(Write Lock):排他锁,仅一个线程可持有,且持有写锁的线程会阻塞其他所有读/写锁请求。
- 锁降级:持有写锁的线程可主动降级为读锁,减少资源竞争。
Java实现类:
import java.util.concurrent.locks.ReentrantReadWriteLock;
二、基础使用示例
场景:缓存系统(高频读取,低频更新)
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Cache {
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private Map<String, String> cache = new HashMap<>();
public String getValue(String key) {
lock.readLock().lock(); // 获取读锁
try {
return cache.get(key);
} finally {
lock.readLock().unlock(); // 释放读锁
}
}
public void putValue(String key, String value) {
lock.writeLock().lock(); // 获取写锁
try {
cache.put(key, value);
} finally {
lock.writeLock().unlock(); // 释放写锁
}
}
}
输出示例:
- 多个线程可同时调用
getValue(),但仅一个线程能执行putValue()。
三、读写锁 vs 互斥锁
| 特性 | 读写锁 (ReentrantReadWriteLock) |
互斥锁 (synchronized) |
|---|---|---|
| 读操作并发性 | 多线程可同时读 | 仅一个线程可访问 |
| 写操作并发性 | 仅一个线程可写 | 仅一个线程可访问 |
| 适用场景 | 读多写少(如缓存、配置读取) | 写多读少或强一致性场景 |
| 性能开销 | 较低(读操作不阻塞其他读) | 较高(所有操作串行化) |
四、高级用法
1. 锁降级(Lock Downgrade)
持有写锁的线程可降级为读锁,减少锁竞争:
java
public void updateCacheWithLockDowngrade(String key, String newValue) {
lock.writeLock().lock(); // 获取写锁
try {
// 写操作
cache.put(key, newValue);
// 降级为读锁(允许其他读操作)
lock.readLock().lock();
lock.writeLock().unlock();
// 继续读操作(如统计缓存大小)
System.out.println("Cache size: " + cache.size());
} finally {
lock.readLock().unlock(); // 释放读锁
}
}
2. 公平锁与非公平锁
-
默认非公平锁:
ReentrantReadWriteLock默认非公平,写锁优先级高于读锁。 -
公平锁配置:构造时传入
true,按请求顺序分配锁:
ReentrantReadWriteLock fairLock = new ReentrantReadWriteLock(true);
3. 尝试获取锁(带超时)
boolean acquired = lock.readLock().tryLock(10, TimeUnit.SECONDS); // 尝试获取读锁,10秒超时
if (acquired) {
try {
// 执行读操作
} finally {
lock.readLock().unlock();
}
} else {
System.out.println("获取读锁失败");
}
五、注意事项与最佳实践
1. 避免嵌套锁
// ❌ 错误示例:在读锁内调用外部方法(可能获取其他锁)
public void unsafeMethod() {
lock.readLock().lock();
try {
someExternalService.call(); // 可能获取其他锁,导致死锁
} finally {
lock.readLock().unlock();
}
}
// ✅ 正确做法:缩小同步范围
public void safeMethod() {
lock.readLock().lock();
try {
// 仅在此处执行必要操作
} finally {
lock.readLock().unlock();
}
}
2. 使用tryLock()避免死锁
if (lock.writeLock().tryLock(1, TimeUnit.SECONDS)) {
try {
// 写操作
} finally {
lock.writeLock().unlock();
}
} else {
System.out.println("写锁获取失败,稍后重试");
}
3. 锁粒度控制
-
细粒度锁:将大锁拆分为多个小锁(如按缓存键分段锁)。
-
示例:
private final Map<String, ReentrantReadWriteLock> locks = new ConcurrentHashMap<>(); public String getValue(String key) { ReentrantReadWriteLock lock = locks.computeIfAbsent(key, k -> new ReentrantReadWriteLock()); lock.readLock().lock(); try { return cache.get(key); } finally { lock.readLock().unlock(); } }
六、适用场景
- 缓存系统:高频读取缓存,低频更新缓存。
- 配置管理:动态配置读取多,修改少。
- 统计类数据:如计数器(读多写少)。
- 大数据分片处理:并行读取不同分片数据,但仅允许一个线程修改元数据。
七、总结
- 优势:读写锁在读多写少场景下性能远超互斥锁。
- 劣势:写操作仍需串行化,复杂场景需谨慎设计。
- 最佳实践:
- 优先使用非公平锁(默认)以提高吞吐量。
- 避免在锁内执行耗时操作或外部调用。
- 结合
tryLock()和超时机制防止死锁。 - 考虑锁降级优化读密集型操作。
通过合理使用读写锁,可以显著提升高并发系统的性能,但需根据具体场景权衡锁的复杂性与收益。