用于读写场景的 ReadWriteLock
使用 ReadWriteLock,在缓存中允许并发读取,同时确保独占写入
用于读写场景的 ReadWriteLock 是 CoddyKit 上的免费 Java Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Java Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Java Academy 课程共包含 4 节课。
读写者问题
多个线程可以同时安全地读取共享数据。但写入需要独占访问——不能同时进行读取或写入。ReadWriteLock 对此进行了建模:多个并发读取者 OR 一个独占写入者。
import java.util.concurrent.locks.*;
ReadWriteLock rwLock = new ReentrantReadWriteLock();
Lock readLock = rwLock.readLock();
Lock writeLock = rwLock.writeLock();读锁:共享访问
只要没有线程持有写锁,多个线程就可以同时持有读锁:
class ReadableCache {
private final Map<String, String> cache = new HashMap<>();
private final ReadWriteLock lock = new ReentrantReadWriteLock();
String get(String key) {
lock.readLock().lock();
try {
return cache.get(key); // concurrent reads OK
} finally {
lock.readLock().unlock();
}
}
}写锁:独占访问
同一时间只能有一个线程持有写锁。写入期间,所有读取者和其他写入者都会被阻塞:
class WritableCache extends ReadableCache {
private final ReadWriteLock wLock = new ReentrantReadWriteLock();
private final Map<String, String> data = new HashMap<>();
void put(String key, String value) {
wLock.writeLock().lock();
try {
data.put(key, value); // exclusive write
} finally {
wLock.writeLock().unlock();
}
}
}完整缓存示例
一个读操作占主导且写操作很少的线程安全缓存:
class Cache<K,V> {
private final Map<K,V> map = new HashMap<>();
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
V get(K key) {
lock.readLock().lock();
try { return map.get(key); }
finally { lock.readLock().unlock(); }
}
void put(K key, V value) {
lock.writeLock().lock();
try { map.put(key, value); }
finally { lock.writeLock().unlock(); }
}
int size() {
lock.readLock().lock();
try { return map.size(); }
finally { lock.readLock().unlock(); }
}
}锁降级
ReadWriteLock 支持锁降级:获取写锁 → 获取读锁 → 释放写锁。这样,线程就能在不释放锁的情况下,从独占写入状态转换为共享读取状态:
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
Lock r = rwl.readLock(), w = rwl.writeLock();
w.lock(); // acquire write
try {
// modify data
r.lock(); // acquire read WHILE holding write
} finally {
w.unlock(); // release write, now holding read only
}
try {
// safely read the just-written data
} finally {
r.unlock();
}读锁不支持升级
锁升级(读 → 写)不受支持。如果持有读锁的线程尝试获取写锁,就会发生死锁,因为写锁会等待 ALL 读取者,包括它自己:
// DEADLOCK: Do NOT do this
lock.readLock().lock();
try {
lock.writeLock().lock(); // DEADLOCK — waits for readLock to release
} finally {
lock.readLock().unlock();
}ReadWriteLock 何时有帮助
在以下情况下,ReadWriteLock 很有优势:
- 读取次数明显多于写入次数
- 读取操作需要较长时间(例如复杂查询)
当写入频繁(写锁会阻塞所有读取者)或操作非常快(两个锁对象会带来额外开销)时,它可能会降低性能。
ReadWriteLock 与同步机制的比较
同步机制/ReentrantLock → 同一时间只有一个线程(即使是并发读取者也会被阻塞)。
ReadWriteLock → 多个并发读取者,一个独占写入者。在读操作占主导的场景中,读取吞吐量更高。
// synchronized: only one thread reads at a time
synchronized String get(String key) { return cache.get(key); }
// ReadWriteLock: many threads can read simultaneously
String get2(String key) {
lock.readLock().lock();
try { return cache.get(key); }
finally { lock.readLock().unlock(); }
}性能注意事项
获取和释放两个锁对象,相比单个锁会增加开销。在假设 ReadWriteLock 更快之前,请先进行性能分析。在读操作占主导且操作耗时较长的场景中,其优势很明显。对于操作快速且写入占主导的工作负载,简单的 ReentrantLock 可能更快。
ConcurrentHashMap 替代方案
对于简单的映射操作,ConcurrentHashMap 通常比 HashMap + ReadWriteLock 更快,因为它使用内部的分段锁,不会产生全局竞争:
// Often better than HashMap + ReadWriteLock for simple operations:
Map<String, String> concurrent = new ConcurrentHashMap<>();
concurrent.put("key", "value"); // thread-safe, no explicit lock
String v = concurrent.get("key"); // thread-safe读锁公平性
默认情况下,ReentrantReadWriteLock 是非公平的(读取者可能导致写入者饥饿)。请使用公平构造方法来防止写入者饥饿:
// Fair: waiting writers are served before new readers
ReentrantReadWriteLock fairLock = new ReentrantReadWriteLock(true);
System.out.println(fairLock.isFair()); // true快速检查
在读操作占主导的应用程序中,ReadWriteLock 相比简单的同步方法,其主要优势是什么?
回顾:ReadWriteLock
关键要点:
- 多个并发读取 OR 一个独占写入——不能两者同时进行
- 使用 readLock.lock()/unlock() 进行共享读取
- 使用 writeLock.lock()/unlock() 进行独占写入
- 支持锁降级;升级(读→写)会导致死锁
- 最适合读取占主导且操作较复杂的场景;简单映射则使用 ConcurrentHashMap
常见问题解答
「用于读写场景的 ReadWriteLock」课时是免费的吗?
是的 — 「用于读写场景的 ReadWriteLock」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Java Academy 课程的其余内容,请升级到 CoddyKit PRO。 Java Academy 课程共包含 4 节课。
「用于读写场景的 ReadWriteLock」这节课中我会学到什么?
使用 ReadWriteLock,在缓存中允许并发读取,同时确保独占写入 你通过在浏览器中直接运行的动手代码来练习 Java Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Java Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Java Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「用于读写场景的 ReadWriteLock」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Java Academy 课中编写并运行代码吗?
能。每节 Java Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- ReentrantLock 与 synchronized
- 用于读写场景的 ReadWriteLock
- 原子变量:无锁更新
- StampedLock 与乐观读取