ReentrantReadWriteLock读写分离与锁降级实践

一、读写锁的核心价值

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

读写锁的优势

  1. 读读并发:多个线程可以同时获取读锁
  2. 读写互斥:写锁独占时阻塞所有读写操作
  3. 写写互斥:同一时刻只允许一个写操作
  4. 锁降级:写锁可安全降级为读锁(本文重点)

二、ReentrantReadWriteLock实现原理

2.1 状态分离设计

ReentrantReadWriteLock通过AQS(AbstractQueuedSynchronizer)实现,其核心在于将32位state分为两部分:

// 状态位拆分示意
static final int SHARED_SHIFT   = 16;       // 共享锁移位值
static final int EXCLUSIVE_MASK = (1 << 16) - 1; // 独占锁掩码

// 获取读锁数量(高16位)
static int sharedCount(int c) { 
    return c >>> SHARED_SHIFT; 
}

// 获取写锁重入次数(低16位)
static int exclusiveCount(int c) { 
    return c & EXCLUSIVE_MASK; 
}

2.2 锁获取规则

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

2.3 工作流程对比

读锁获取流程:

1. 检查是否有写锁持有
   ├─ 无:增加读锁计数,获取成功
   └─ 有:检查是否当前线程持有
        ├─ 是:获取成功(锁降级情况)
        └─ 否:进入等待队列

写锁获取流程:

1. 检查是否有任何锁
   ├─ 无:设置写锁状态,获取成功
   └─ 有:检查是否当前线程重入
        ├─ 是:增加写锁计数
        └─ 否:进入等待队列

三、锁降级:原理与必要性

3.1 什么是锁降级?

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

  1. 获取读锁
  2. 释放写锁
  3. 在仅持有读锁的状态下继续操作
// 标准锁降级流程
writeLock.lock();          // 1.获取写锁
try {
    // 修改数据...
    readLock.lock();       // 2.获取读锁(关键步骤)
} finally {
    writeLock.unlock();    // 3.释放写锁(完成降级)
}

try {
    // 读取数据(受读锁保护)
} finally {
    readLock.unlock();     // 4.释放读锁
}

3.2 为什么需要锁降级?

考虑以下无锁降级的危险场景:

时间线:
1. 线程A获取写锁
2. 线程A修改数据
3. 线程A释放写锁
4. [危险间隙开始]
5. 线程B获取写锁
6. 线程B修改数据
7. 线程B释放写锁
8. [危险间隙结束]
9. 线程A获取读锁
10. 线程A读取到线程B修改的数据(非预期!)

锁降级通过在释放写锁前获取读锁,消除了这个危险间隙:

时间线:
1. 线程A获取写锁
2. 线程A修改数据
3. 线程A获取读锁
4. 线程A释放写锁
5. [读锁保护中]
6. 线程B尝试获取写锁(阻塞)
7. 线程A安全读取数据
8. 线程A释放读锁
9. 线程B获取写锁

3.3 锁降级的核心价值

  1. 数据一致性:确保线程看到自己修改的最新数据
  2. 写后读原子性:消除写锁释放到读锁获取之间的危险窗口
  3. 并发性优化:允许其他读线程并发访问最新数据

四、完整代码示例

4.1 基础读写锁使用

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockDemo {
    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
    private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
    private int sharedData = 0;

    // 写操作
    public void writeData(int value) {
        writeLock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " 开始写入: " + value);
            sharedData = value;
            Thread.sleep(100); // 模拟写耗时
            System.out.println(Thread.currentThread().getName() + " 写入完成");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            writeLock.unlock();
        }
    }

    // 读操作
    public void readData() {
        readLock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " 开始读取");
            Thread.sleep(50); // 模拟读耗时
            System.out.println(Thread.currentThread().getName() + " 读取到: " + sharedData);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            readLock.unlock();
        }
    }

    public static void main(String[] args) {
        ReadWriteLockDemo demo = new ReadWriteLockDemo();
        
        // 创建读线程
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                while (true) {
                    demo.readData();
                    sleep(200);
                }
            }, "Reader-" + i).start();
        }
        
        // 创建写线程
        for (int i = 0; i < 2; i++) {
            int id = i;
            new Thread(() -> {
                int value = 0;
                while (true) {
                    demo.writeData(value++);
                    sleep(300);
                }
            }, "Writer-" + id).start();
        }
    }
    
    private static void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

执行效果说明:

Reader-0 开始读取
Reader-1 开始读取   // 多个读线程可以并发
Reader-0 读取到: 0
Reader-1 读取到: 0
Writer-0 开始写入: 0  // 写操作独占
Writer-0 写入完成
Reader-2 开始读取
Reader-3 开始读取   // 写完成后读操作恢复并发
Reader-2 读取到: 0
Reader-3 读取到: 0

4.2 锁降级实战

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 + " <<<");
        sleep(300);
    }

    // 干扰线程:尝试修改数据
    public void disturb() {
        writeLock.lock();
        try {
            System.out.println("\t[" + Thread.currentThread().getName() + "] 干扰线程获取写锁!");
            criticalData = -1; // 破坏数据
            dataValid = false;
        } finally {
            writeLock.unlock();
        }
    }

    public static void main(String[] args) {
        LockDowngradeDemo demo = new LockDowngradeDemo();
        
        // 主工作线程(执行锁降级)
        new Thread(() -> demo.processWithDowngrade(), "MainWorker").start();
        
        sleep(100); // 确保主线程先启动
        
        // 干扰线程
        new Thread(() -> {
            System.out.println("\t[Disturber] 尝试干扰...");
            demo.disturb();
            System.out.println("\t[Disturber] 干扰完成");
        }, "Disturber").start();
    }
    
    private static void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

执行效果说明:

[MainWorker] 获取写锁,准备数据...
[Disturber] 尝试干扰...     // 干扰线程启动
[MainWorker] 获取读锁(准备降级)
[MainWorker] 释放写锁(完成降级)
[MainWorker] 在降级保护下使用数据
>>> 使用关键数据: 742 <<<   // 数据未被干扰
[MainWorker] 释放读锁
[Disturber] 干扰线程获取写锁! // 此时才获取写锁
[Disturber] 干扰完成

4.3 错误示例:忘记锁降级

public void flawedProcess() {
    writeLock.lock();
    try {
        prepareData();
    } finally {
        writeLock.unlock();  // 危险:先释放写锁
    }
    
    // 此时其他线程可能修改数据!
    readLock.lock();  
    try {
        useData();  // 可能使用过期数据
    } finally {
        readLock.unlock();
    }
}

风险分析:

时间线:
1. 线程A获取写锁
2. 线程A修改数据
3. 线程A释放写锁
4. [危险间隙]
5. 线程B获取写锁
6. 线程B修改数据
7. 线程B释放写锁
8. 线程A获取读锁
9. 线程A读取到过期数据(线程B修改后的数据)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值