读写锁适合什么场景,一定比互斥锁快吗?
未标记
考查重点:读写比例与锁管理开销。读多且临界区长可获益,短操作或频繁写可能更慢,用真实负载比较
本题目录
参考答案
核心回答
读写锁适合共享数据读多写少、读操作确实不改变状态,并且多个读线程可以同时做足够多有效工作的场景。它允许读读并行,写操作则与其他读写互斥。性能是否优于互斥锁,还取决于临界区长度、竞争程度、读写比例和锁实现开销,必须测量。
什么情况下可能有收益
例如一份较少更新的配置映射,多个请求需要在一致快照上完成一段查询。读锁保护读取期间数据不被修改,写锁保证更新独占。若读临界区有一定工作量,多个核上的读者并行可能抵消锁管理成本。
但“调用的是 get”不代表它一定是只读。例如 LRU 缓存的读取可能同时更新访问顺序,仍然在修改共享链表,不能简单地只加读锁。还要检查统计计数、惰性初始化和返回可变对象后逃出锁范围等隐含写操作。
为什么可能更慢
读写锁需要维护读者计数、写者状态、等待与公平策略,获取读锁本身也有同步开销。如果读操作只是一次很短的取值,锁成本可能占主导;写入频繁时读者又不断被阻塞,并行收益很少。长时间持有读锁还可能让写线程等待很久。
ReentrantReadWriteLock 默认非公平模式,不保证读写者的获取顺序;持续竞争下可能出现某类线程长期推迟。公平模式会更强调等待顺序,但不能保证操作系统调度公平,也可能牺牲吞吐。应同时观察读吞吐和写等待的尾延迟。
升降级有什么限制
持有写锁时,可以先获取读锁,再释放写锁,完成锁降级,保持更新到读取之间的保护。直接持有读锁再等待写锁则不受支持;多个读者都这样做可能互相等待。
需要从读转写时,应先释放读锁,获取写锁后重新检查条件,因为中间状态可能已被其他线程改变。不能认为之前在读锁下检查过就永远成立。
怎样选和验证
简单结构先考虑普通互斥、并发容器或不可变快照;明确有大量可并行读、更新较少时再评估读写锁。用真实读写比例和数据规模测吞吐、P95/P99、写等待及 CPU,覆盖热点和突发写入。StampedLock 的乐观读是另一种选择,但需校验与重读,不能直接当作无条件更快的替代。
参考资料
- 国内:JavaGuide / Snailclimb:Java 锁详解:读写锁:参考读写锁的互斥规则与管理成本,解释正文中读多写少为何仍需评估临界区长度、竞争和同步开销。
- 国内:JavaGuide / Snailclimb:Java 并发常见问题(二):借鉴读写锁与升级限制的连续问答,说明正文中的适用场景与持锁操作顺序。
- 国内:JavaGuide / Snailclimb:AQS 详解:共享与独占同步的结构说明多个读者共同持有与写者独占所需的状态管理。
- 国外:Oracle:ReadWriteLock API, SE21:官方列出的读写频率、操作时长和竞争因素直接解释为何读写锁不必然更快。
- 国外:Oracle:ReentrantReadWriteLock API, SE21:官方规则用于核对等待公平性及先获得读锁再释放写锁的降级过程。
