0Pricing
Java Academy · 课时

用于读写场景的 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 反馈 — 无需本地设置。

此课程中的所有课时

  1. ReentrantLock 与 synchronized
  2. 用于读写场景的 ReadWriteLock
  3. 原子变量:无锁更新
  4. StampedLock 与乐观读取
← 返回 Java Academy