找回密码
 立即注册
首页 业界区 业界 【深入理解ReentrantReadWriteLock】读写分离与锁降级实 ...

【深入理解ReentrantReadWriteLock】读写分离与锁降级实践

洪思思 2025-7-14 14:41:21
一、读写锁的核心价值

在多线程编程中,同步机制是保证线程安全的关键。传统的互斥锁(如synchronized)在读多写少的场景下存在明显性能瓶颈:读操作被不必要的串行化,即使多个线程只读取数据也会相互阻塞。这正是ReentrantReadWriteLock的用武之地!
读写锁的优势


  • 读读并发:多个线程可以同时获取读锁
  • 读写互斥:写锁独占时阻塞所有读写操作
  • 写写互斥:同一时刻只允许一个写操作
  • 锁降级:写锁可安全降级为读锁(本文重点)
二、ReentrantReadWriteLock实现原理

2.1 状态分离设计

ReentrantReadWriteLock通过AQS(AbstractQueuedSynchronizer)实现,其核心在于将32位state分为两部分:
  1. // 状态位拆分示意
  2. static final int SHARED_SHIFT   = 16;       // 共享锁移位值
  3. static final int EXCLUSIVE_MASK = (1 << 16) - 1; // 独占锁掩码
  4. // 获取读锁数量(高16位)
  5. static int sharedCount(int c) {
  6.     return c >>> SHARED_SHIFT;
  7. }
  8. // 获取写锁重入次数(低16位)
  9. static int exclusiveCount(int c) {
  10.     return c & EXCLUSIVE_MASK;
  11. }
复制代码
2.2 锁获取规则

锁类型获取条件读锁无写锁持有,或持有写锁的是当前线程(锁降级情况)写锁无任何读锁且无其他线程持有写锁(可重入)2.3 工作流程对比

读锁获取流程:
  1. 1. 检查是否有写锁持有
  2.    ├─ 无:增加读锁计数,获取成功
  3.    └─ 有:检查是否当前线程持有
  4.         ├─ 是:获取成功(锁降级情况)
  5.         └─ 否:进入等待队列
复制代码
写锁获取流程:
  1. 1. 检查是否有任何锁
  2.    ├─ 无:设置写锁状态,获取成功
  3.    └─ 有:检查是否当前线程重入
  4.         ├─ 是:增加写锁计数
  5.         └─ 否:进入等待队列
复制代码
三、锁降级:原理与必要性

3.1 什么是锁降级?

锁降级(Lock Downgrading) 是指线程在持有写锁的情况下:

  • 获取读锁
  • 释放写锁
  • 在仅持有读锁的状态下继续操作
  1. // 标准锁降级流程
  2. writeLock.lock();          // 1.获取写锁
  3. try {
  4.     // 修改数据...
  5.     readLock.lock();       // 2.获取读锁(关键步骤)
  6. } finally {
  7.     writeLock.unlock();    // 3.释放写锁(完成降级)
  8. }
  9. try {
  10.     // 读取数据(受读锁保护)
  11. } finally {
  12.     readLock.unlock();     // 4.释放读锁
  13. }
复制代码
3.2 为什么需要锁降级?

考虑以下无锁降级的危险场景:
  1. 时间线:
  2. 1. 线程A获取写锁
  3. 2. 线程A修改数据
  4. 3. 线程A释放写锁
  5. 4. [危险间隙开始]
  6. 5. 线程B获取写锁
  7. 6. 线程B修改数据
  8. 7. 线程B释放写锁
  9. 8. [危险间隙结束]
  10. 9. 线程A获取读锁
  11. 10. 线程A读取到线程B修改的数据(非预期!)
复制代码
锁降级通过在释放写锁前获取读锁,消除了这个危险间隙:
  1. 时间线:
  2. 1. 线程A获取写锁
  3. 2. 线程A修改数据
  4. 3. 线程A获取读锁
  5. 4. 线程A释放写锁
  6. 5. [读锁保护中]
  7. 6. 线程B尝试获取写锁(阻塞)
  8. 7. 线程A安全读取数据
  9. 8. 线程A释放读锁
  10. 9. 线程B获取写锁
复制代码
3.3 锁降级的核心价值


  • 数据一致性:确保线程看到自己修改的最新数据
  • 写后读原子性:消除写锁释放到读锁获取之间的危险窗口
  • 并发性优化:允许其他读线程并发访问最新数据
四、完整代码示例

4.1 基础读写锁使用
  1. import java.util.concurrent.locks.ReentrantReadWriteLock;
  2. public class ReadWriteLockDemo {
  3.     private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
  4.     private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
  5.     private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
  6.     private int sharedData = 0;
  7.     // 写操作
  8.     public void writeData(int value) {
  9.         writeLock.lock();
  10.         try {
  11.             System.out.println(Thread.currentThread().getName() + " 开始写入: " + value);
  12.             sharedData = value;
  13.             Thread.sleep(100); // 模拟写耗时
  14.             System.out.println(Thread.currentThread().getName() + " 写入完成");
  15.         } catch (InterruptedException e) {
  16.             Thread.currentThread().interrupt();
  17.         } finally {
  18.             writeLock.unlock();
  19.         }
  20.     }
  21.     // 读操作
  22.     public void readData() {
  23.         readLock.lock();
  24.         try {
  25.             System.out.println(Thread.currentThread().getName() + " 开始读取");
  26.             Thread.sleep(50); // 模拟读耗时
  27.             System.out.println(Thread.currentThread().getName() + " 读取到: " + sharedData);
  28.         } catch (InterruptedException e) {
  29.             Thread.currentThread().interrupt();
  30.         } finally {
  31.             readLock.unlock();
  32.         }
  33.     }
  34.     public static void main(String[] args) {
  35.         ReadWriteLockDemo demo = new ReadWriteLockDemo();
  36.         
  37.         // 创建读线程
  38.         for (int i = 0; i < 5; i++) {
  39.             new Thread(() -> {
  40.                 while (true) {
  41.                     demo.readData();
  42.                     sleep(200);
  43.                 }
  44.             }, "Reader-" + i).start();
  45.         }
  46.         
  47.         // 创建写线程
  48.         for (int i = 0; i < 2; i++) {
  49.             int id = i;
  50.             new Thread(() -> {
  51.                 int value = 0;
  52.                 while (true) {
  53.                     demo.writeData(value++);
  54.                     sleep(300);
  55.                 }
  56.             }, "Writer-" + id).start();
  57.         }
  58.     }
  59.    
  60.     private static void sleep(long millis) {
  61.         try {
  62.             Thread.sleep(millis);
  63.         } catch (InterruptedException e) {
  64.             Thread.currentThread().interrupt();
  65.         }
  66.     }
  67. }
复制代码
执行效果说明:
  1. Reader-0 开始读取
  2. Reader-1 开始读取   // 多个读线程可以并发
  3. Reader-0 读取到: 0
  4. Reader-1 读取到: 0
  5. Writer-0 开始写入: 0  // 写操作独占
  6. Writer-0 写入完成
  7. Reader-2 开始读取
  8. Reader-3 开始读取   // 写完成后读操作恢复并发
  9. Reader-2 读取到: 0
  10. Reader-3 读取到: 0
复制代码
4.2 锁降级实战

[code]import java.util.concurrent.locks.ReentrantReadWriteLock;public class LockDowngradeDemo {    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();    private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();    private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();    private volatile boolean dataValid = false;    private int criticalData = 0;    public void processWithDowngrade() {        // 1. 获取写锁        writeLock.lock();        try {            // 2. 准备数据(写操作)            System.out.println("[" + Thread.currentThread().getName() + "] 获取写锁,准备数据...");            prepareData();                        // 3. 获取读锁(开始降级)            readLock.lock();            System.out.println("[" + Thread.currentThread().getName() + "] 获取读锁(准备降级)");        } finally {            // 4. 释放写锁(保留读锁)            writeLock.unlock();            System.out.println("[" + Thread.currentThread().getName() + "] 释放写锁(完成降级)");        }        try {            // 5. 使用数据(读操作)            System.out.println("[" + Thread.currentThread().getName() + "] 在降级保护下使用数据");            useData();        } finally {            // 6. 释放读锁            readLock.unlock();            System.out.println("[" + Thread.currentThread().getName() + "] 释放读锁");        }    }    private void prepareData() {        // 模拟数据准备(写操作)        criticalData = (int) (Math.random() * 1000);        dataValid = true;        sleep(500); // 模拟耗时操作    }    private void useData() {        if (!dataValid) {            System.err.println("数据无效!");            return;        }                // 模拟数据使用(读操作)        System.out.println(">>> 使用关键数据: " + criticalData + "
您需要登录后才可以回帖 登录 | 立即注册