ReadWriteLock的核心目的是让多个读线程同时执行、互不阻塞,而写线程独占访问;其读共享、写独占语义通过readLock()和writeLock()实现,读-读不互斥,写-写、读-写均互斥。

Java 中使用 ReadWriteLock 的核心目的,就是让**多个读线程可以同时执行,互不阻塞**,而写线程独占访问、排斥所有其他读写操作。所谓“彻底消除读操作之间的互斥阻塞”,本质上是通过锁的语义设计实现的——只要没有写操作在进行或等待(取决于具体实现策略),读与读之间就完全不加锁、不排队、不等待。
理解 ReadWriteLock 的读共享、写独占语义
ReadWriteLock 接口本身不提供实现,它定义了两个锁:一个读锁(readLock())、一个写锁(writeLock())。关键行为如下:
- 多个线程可同时持有读锁(只要没写锁被占用)→ 读-读不互斥
- 写锁是排他的:写-写、写-读、读-写均互斥
- 默认非公平策略下,读锁可能被正在等待的写锁“插队”(即写饥饿),但读与读之间仍无阻塞
- 只要没线程持有写锁,新来的读线程立刻获得读锁,无需等待其他读线程
用 ReentrantReadWriteLock 正确实现读不阻塞
标准实现是 java.util.concurrent.locks.ReentrantReadWriteLock。要确保读操作真正“零互斥”,需注意以下几点:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
- 所有读操作必须统一使用
readLock().lock()/unlock(),不能混用 synchronized 或其他锁 - 读锁必须成对出现,推荐用 try-finally 保证释放:
lock.readLock().lock();<br>try { /* 读逻辑 */ } finally { lock.readLock().unlock(); } - 避免在持读锁时调用可能升级为写操作的方法(如某些集合的 computeIfAbsent,内部可能触发写),否则易导致死锁
- 不要在读锁内长时间执行耗时操作(如 I/O、远程调用),否则会阻塞后续写请求,间接影响系统吞吐
警惕“看似读、实则写”的陷阱
有些操作表面是读,但底层会修改状态,破坏读锁的安全假设。典型例子:
立即学习“Java免费学习笔记(深入)”;
-
ConcurrentHashMap.size()是安全的,但HashMap.size()在非并发容器中不是线程安全读,更不能套在读锁里“假装安全” -
CopyOnWriteArrayList.get(i)本质是无锁读,不需要额外加读锁;若强行包裹读锁,反而引入不必要的开销和潜在阻塞 - 自定义对象中,getter 方法如果内部触发懒加载、缓存更新、统计计数++等,就不是纯读 → 必须用写锁或原子变量替代
进阶:选择公平策略或降级写锁以优化场景
默认非公平模式下读性能最优,但写线程可能长期等待。若业务要求写响应及时,可启用公平模式:new ReentrantReadWriteLock(true)
此时读写线程按 FIFO 排队,读-读依然不互斥,只是读请求需排队获取锁(但排队不等于阻塞——一旦轮到,立即成功,无等待其他读线程)。
对于“读多写少 + 写后紧接读”的场景,还可利用锁降级:先获写锁 → 修改数据 → 不释放写锁,直接获取读锁 → 释放写锁 → 继续读。这能保证写后读看到最新值,且期间无其他写入干扰。


















