ReadWriteLock.readLock()返回的是需显式lock()/unlock()的Lock实例,读锁间不互斥但与写锁互斥,不可升级,公平性影响吞吐与饥饿,tryLock()仅限制获取等待而非持有时间。

ReadWriteLock.readLock() 返回的锁对象必须显式调用 lock() 和 unlock()
很多人以为 readLock() 本身就能“上锁”,其实它只返回一个 Lock 实例,和 ReentrantLock 行为一致:不调用 lock() 就不会阻塞,不配对调用 unlock() 就会导致锁泄漏。常见错误是只写 rwLock.readLock() 而没后续操作,结果读操作完全没受保护。
正确做法是:
- 用
try ... finally包裹读逻辑,确保unlock()必然执行 - 避免在
lock()前抛异常导致跳过加锁(虽不影响正确性,但语义混乱) - 不要把
readLock()存为类字段反复复用——它本身无状态,但易引发误用(比如多处共用同一引用却漏调unlock())
读锁之间不互斥,但读锁与写锁互斥
这是 ReadWriteLock 的核心契约:多个线程可同时持有 readLock(),但只要有一个线程持有了 writeLock(),所有后续 readLock().lock() 都会阻塞,直到写锁释放。反过来,若有线程已持读锁,writeLock().lock() 也会被挂起。
典型陷阱:
- 在读锁保护的代码块里调用可能触发写操作的外部方法(比如某个 getter 内部悄悄更新了缓存),造成死锁或数据不一致
- 误以为“读锁能防止其他线程写”——它只阻止新写锁获取,不阻止已持有的写锁继续运行
- 忽略锁降级:JDK 不支持直接从读锁升级为写锁(会死锁),如需降级,必须先释放读锁再抢写锁(且需重检数据是否仍有效)
使用 ReentrantReadWriteLock 时要注意公平性与性能权衡
ReentrantReadWriteLock 构造函数支持传入 boolean fair 参数,默认为 false(非公平)。非公平模式下,新请求的读锁可能插队已等待的写锁,导致写线程饥饿;公平模式则严格按 FIFO 排队,但吞吐量下降约 10–30%(实测取决于争用强度)。
建议:
- 读多写少、且写操作延迟敏感的场景(如配置热更新),用非公平模式
- 写操作较重、或要求强响应确定性的场景(如金融账务批处理),启用公平模式
- 不要在构造后动态切换公平性——它是不可变属性
tryLock() 在读锁场景下容易误判超时条件
readLock().tryLock(long, TimeUnit) 的超时逻辑常被误解:它只控制“等待获取锁”的时间,不是“持有锁”的时间。一旦成功获取,即使后续读操作耗时远超 timeout,也不会自动释放锁。
更隐蔽的问题:
- 多个线程并发调用
tryLock(1, SECONDS),可能全部失败(因写锁长期占用),但错误日志里只显示“timeout”,掩盖了真实瓶颈是写操作太慢 - 未检查
tryLock()返回值就直接执行读逻辑,导致部分线程绕过锁保护,引发并发问题 - 在循环中反复
tryLock()+sleep(),不如用阻塞式lock()更简洁(除非业务明确要求快速失败)
ReadWriteLock 实例管理,且所有读操作都走它的 readLock() 流程;任何绕过、漏调、或混用不同实例的行为,都会让这个保证失效。

















