面试知识库
进阶

读写锁原理#

一句话答案#

ReadWriteLock 读共享写独占,state 高 16 位读锁低 16 位写锁,支持锁降级(写→读)不支持升级(会死锁)。

核心要点

ReentrantReadWriteLock 核心设计:

  • state 拆分:int 32位 = 高16位(读锁持有数)+ 低16位(写锁重入数)
  • 读锁:共享模式,多个线程可同时持有
  • 写锁:独占模式,有写锁时其他读/写都阻塞

获取规则:

当前状态获取读锁获取写锁
无锁
有读锁❌(等待)
有写锁(非自己)❌(等待)❌(等待)
有写锁(自己)✅(锁降级)✅(重入)

锁降级:写锁 → 获取读锁 → 释放写锁(安全降级,保证数据可见性) 锁升级:不支持!(读锁→写锁会死锁)

适用场景: 缓存、配置管理(读多写少) 缺陷: 写线程可能饥饿 → JDK8 引入 StampedLock(乐观读)

面试回答(2分钟版)

ReentrantReadWriteLock 实现了读共享写独占的锁机制,适用于读多写少场景。它基于 AQS,巧妙地将 state 的 32 位拆成高 16 位记录读锁持有数、低 16 位记录写锁重入数。多个线程可以同时获取读锁并发读取,但有线程持有写锁时其他线程的读写请求都会阻塞,保证写操作的独占性。一个重要特性是支持锁降级:持有写锁的线程可以再获取读锁,然后释放写锁,安全地从写模式切换到读模式,保证数据修改后立即对当前线程可见。但不支持锁升级,如果读锁线程尝试获取写锁会造成死锁,因为写锁要等所有读锁释放,而自己持有读锁不会释放。读写锁的一个缺陷是写线程可能饥饿:大量读线程持续进入导致写线程长期等不到锁。JDK 8 引入了 StampedLock 来解决这个问题,它提供乐观读模式,读操作不加锁而是获取一个 stamp 版本号,读完后验证版本号是否被写操作改变,没改变就直接用,改变了再退化为悲观读锁。

追问与易错

追问方向:

  • “读写锁饥饿怎么解决?”→ 使用公平模式 new ReentrantReadWriteLock(true) 让写线程按顺序获锁;或升级到 JDK8 的 StampedLock,其乐观读模式不阻塞写线程
  • “锁降级的意义?”→ 持有写锁时先获取读锁再释放写锁,保证当前线程能立即看到自己的修改结果,同时允许其他线程并发读;如果直接释放写锁再获取读锁,中间可能被其他写线程修改数据
  • “StampedLock 的区别?”→ StampedLock 提供乐观读模式,读操作不加锁只获取 stamp 版本号,读完后验证版本是否变化,没变则直接用,变了退化为悲观读锁;性能优于 ReadWriteLock 但不支持重入和 Condition

易错点:

  • ❌ 读锁可以升级为写锁——不支持会死锁!
  • ❌ 读写锁一定比 synchronized 快——读少写多时反而慢