读写锁
2025/10/19大约 4 分钟
一、什么是读写锁
读写锁(Read-Write Lock)是一种比普通互斥锁(Mutex)更细粒度的并发控制机制。它允许多个线程 同时读取共享资源,但在有线程执行写操作时,会 独占锁,阻塞其他读写线程。
读写锁通常包含两种状态:
- 读锁(共享锁,Read Lock):多个线程可以同时获取读锁,前提是没有线程持有写锁。
- 写锁(独占锁,Write Lock):当线程持有写锁时,其他线程无法再获取任何锁(无论是读锁还是写锁)。
二、为什么需要读写锁
在许多应用中,读操作的频率远高于写操作。如果仅使用普通的互斥锁(synchronized 或 ReentrantLock),即使是多个读取操作,也必须串行执行,这会造成性能浪费。
而读写锁通过区分“读”和“写”两种访问方式,让系统在读多写少的场景下获得更高的并发性能:
- 多个读线程可以 并发读取 数据;
- 写操作仍保持独占,防止并发写入造成数据不一致。
常见的使用场景包括:配置中心、缓存系统、搜索引擎索引加载等 读多写少 的高并发系统。
三、读写锁如何解决并发问题
- 读多写少优化:多个线程可以同时获取读锁,显著提升系统吞吐量。
- 写操作独占:在写操作执行期间,禁止其他读写线程访问资源,保证数据一致性。
- 策略可控:读写锁可设置为“读优先”或“写优先”,从而在性能与公平性之间做平衡。
四、读写锁下写锁的获取逻辑
当当前存在读锁时,如果有线程尝试获取写锁,会出现以下情况:
- 写锁会被 阻塞等待,直到所有读锁都被释放。
- 一旦读锁全部释放,写锁才能成功获取并执行写操作。
- 写操作完成后,其他线程才能重新获取读锁或写锁。
举个例子:
| 时间 | 操作 | 锁状态 | 说明 |
|---|---|---|---|
| T1 | ThreadA 获取读锁 | 读锁数 = 1 | ✅ 允许 |
| T2 | ThreadB 获取读锁 | 读锁数 = 2 | ✅ 允许 |
| T3 | ThreadC 尝试获取写锁 | 等待中 | ❌ 阻塞直到所有读锁释放 |
| T4 | ThreadA、B 释放读锁 | 读锁数 = 0 | ✅ ThreadC 获得写锁 |
也就是说,写锁的获取必须等待读锁全部释放后才能成功。
这种设计保证了写操作的 独占性与一致性,防止写线程在其他线程读取时修改数据,导致数据不稳定或部分可见。
五、写锁饥饿问题与公平模式
在默认(非公平)模式下,系统更倾向于优先满足新的读请求,从而提升整体读性能。
但如果读操作持续不断,写线程可能长期被阻塞,形成 写锁饥饿(Write Starvation) 问题。
Java 的 ReentrantReadWriteLock 提供了 公平模式 来解决该问题:
ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);true表示公平锁:读写锁会按照请求顺序获取,防止写线程被读线程长期“压制”。false(默认)为非公平锁:提升读性能,但存在写锁饥饿风险。
六、Java 中的实现示例
Java 提供了 ReentrantReadWriteLock 类来支持可重入读写锁机制:
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
Lock readLock = rwLock.readLock();
Lock writeLock = rwLock.writeLock();
// 读操作
readLock.lock();
try {
// 执行读取逻辑
} finally {
readLock.unlock();
}
// 写操作
writeLock.lock();
try {
// 执行写入逻辑
} finally {
writeLock.unlock();
}特点:
- 读锁可被多个线程同时持有。
- 写锁获取时会阻塞所有的读线程和写线程。
- 写操作期间的锁持有具有完全独占性。
七、总结
读写锁通过将锁粒度细化为“读锁”和“写锁”,在保证数据一致性的同时,大幅提升系统的并发性能:
- 读操作共享,多个线程可同时访问数据。
- 写操作独占,防止数据不一致。
- 写锁会等待所有读锁释放后再执行,确保写入安全。
- 可选择 公平模式 以避免写锁饥饿。
在“读多写少”的并发场景下,读写锁是实现高性能、线程安全访问的首选机制。