022:ReentrantReadWriteLock深度解析:如何优雅实现读写分离锁

ReentrantReadWriteLock读写锁深度解析

🥂(❁´◡`❁)您的点赞👍➕评论📝➕收藏⭐➕关注👀是作者创作的最大动力🤞

💖📕🎉🔥 支持我:点赞👍+收藏⭐️+留言📝+关注👀欢迎留言讨论

🔥🔥🔥(源码获取 + 调试运行 + 问题答疑)🔥🔥🔥  有兴趣可以联系我

🔥🔥🔥  文末有往期免费源码,直接领取获取(无删减,无套路)

我们常常在当下感到时间慢,觉得未来遥远,但一旦回头看,时间已经悄然流逝。对于未来,尽管如此,也应该保持一种从容的态度,相信未来仍有许多可能性等待着我们。

🔥🔥🔥(免费,无删减,无套路):java swing管理系统源码 程序 代码 图形界面(11套)」
链接:https://pan.quark.cn/s/784a0d377810
提取码:见文章末尾
🔥🔥🔥(免费,无删减,无套路): Python源代码+开发文档说明(23套)」
链接:https://pan.quark.cn/s/1d351abbd11c
提取码:见文章末尾

🔥🔥🔥(免费,无删减,无套路):计算机专业精选源码+论文(26套)」
链接:https://pan.quark.cn/s/8682a41d0097
提取码:见文章末尾
🔥🔥🔥(免费,无删减,无套路):Java web项目源码整合开发ssm(30套)
链接:https://pan.quark.cn/s/1c6e0826cbfd
提取码:见文章末尾

🔥🔥🔥(免费,无删减,无套路):「在线考试系统源码(含搭建教程)」

链接:https://pan.quark.cn/s/96c4f00fdb43
提取码:见文章末尾


引言:读写锁的必要性

在多线程编程中,我们常常面临这样的困境:如何在高并发读取的场景下,既保证数据的一致性,又不牺牲系统性能?传统的互斥锁(如synchronized)虽然安全,但在读多写少的场景下显得力不从心。这正是ReentrantReadWriteLock大显身手的时刻。

ReentrantReadWriteLock核心概念

读写锁的基本规则

ReentrantReadWriteLock遵循三个基本原则:

  1. 读-读不互斥:多个线程可以同时持有读锁

  2. 读-写互斥:写锁持有期间,所有读锁和写锁请求都会被阻塞

  3. 写-写互斥:同一时刻只能有一个线程持有写锁

类结构概览

public class ReentrantReadWriteLock implements ReadWriteLock {
     private final ReadLock readerLock;
     private final WriteLock writerLock;
     private final Sync sync;
     
     public ReentrantReadWriteLock() {
         this(false); // 默认非公平模式
     }
     
     public ReentrantReadWriteLock(boolean fair) {
         sync = fair ? new FairSync() : new NonfairSync();
         readerLock = new ReadLock(this);
         writerLock = new WriteLock(this);
     }
     
     public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
     public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }
 }

基本使用模式

典型应用场景

 public class ThreadSafeDataContainer {
     private final Map<String, Object> data = new HashMap<>();
     private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
     private final Lock readLock = rwLock.readLock();
     private final Lock writeLock = rwLock.writeLock();
     
     // 读操作 - 使用读锁
     public Object get(String key) {
         readLock.lock();
         try {
             return data.get(key);
         } finally {
             readLock.unlock();
         }
     }
     
     // 写操作 - 使用写锁
     public void put(String key, Object value) {
         writeLock.lock();
         try {
             data.put(key, value);
         } finally {
             writeLock.unlock();
         }
     }
     
     // 复合操作 - 使用写锁
     public Object putIfAbsent(String key, Object value) {
         writeLock.lock();
         try {
             Object current = data.get(key);
             if (current == null) {
                 data.put(key, value);
             }
             return current;
         } finally {
             writeLock.unlock();
         }
     }
 }

锁的降级:一个高级特性

锁降级是ReentrantReadWriteLock的一个重要特性,允许在持有写锁的情况下获取读锁,然后释放写锁,从而将写锁"降级"为读锁。

 public class LockDowngradeDemo {
     private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
     private final Lock readLock = rwLock.readLock();
     private final Lock writeLock = rwLock.writeLock();
     private volatile boolean updateAvailable = false;
     private Object data;
     
     public void processData() {
         readLock.lock();
         if (!updateAvailable) {
             // 必须释放读锁才能获取写锁
             readLock.unlock();
             writeLock.lock();
             try {
                 // 重新检查状态,因为可能在此期间被其他线程修改
                 if (!updateAvailable) {
                     // 执行数据更新
                     data = fetchDataFromSource();
                     updateAvailable = true;
                 }
                 // 在释放写锁之前获取读锁 - 锁降级
                 readLock.lock();
             } finally {
                 writeLock.unlock(); // 写锁释放,但仍然持有读锁
             }
         }
         
         try {
             // 继续使用数据,仍然受到读锁保护
             useData(data);
         } finally {
             readLock.unlock();
         }
     }
     
     private Object fetchDataFromSource() { /* 数据获取逻辑 */ }
     private void useData(Object data) { /* 数据使用逻辑 */ }
 }

深入原理:状态设计与锁获取机制

同步状态的设计

ReentrantReadWriteLock使用一个32位的整型变量state来同时管理读锁和写锁的状态:

  • 高16位:表示读锁的持有数量

  • 低16位:表示写锁的重入次数

 static final int SHARED_SHIFT   = 16;
 static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
 static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
 static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
 ​
 // 获取读锁数量
 static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
 // 获取写锁数量
 static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

读锁获取逻辑

 protected final int tryAcquireShared(int unused) {
     Thread current = Thread.currentThread();
     int c = getState();
     
     // 如果有写锁被持有,且持有者不是当前线程,获取失败
     if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current)
         return -1;
     
     int r = sharedCount(c);
     if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) {
         // 成功获取读锁
         if (r == 0) {
             firstReader = current;
             firstReaderHoldCount = 1;
         } else if (firstReader == current) {
             firstReaderHoldCount++;
         } else {
             // 更新读锁持有计数
         }
         return 1;
     }
     return fullTryAcquireShared(current);
 }

写锁获取逻辑

 protected final boolean tryAcquire(int acquires) {
     Thread current = Thread.currentThread();
     int c = getState();
     int w = exclusiveCount(c);
     
     if (c != 0) {
         // 存在读锁或写锁被其他线程持有
         if (w == 0 || current != getExclusiveOwnerThread())
             return false;
         if (w + exclusiveCount(acquires) > MAX_COUNT)
             throw new Error("Maximum lock count exceeded");
     }
     
     if ((w == 0 && writerShouldBlock()) || !compareAndSetState(c, c + acquires))
         return false;
     
     setExclusiveOwnerThread(current);
     return true;
 }

写锁优先机制解析

问题的核心:防止写线程饥饿

"写锁请求后,后续的读锁请求会被阻塞" 这一机制的核心目的是防止写线程饥饿问题。

考虑以下场景:

  1. 系统中持续有读请求到达

  2. 读锁可以共享,因此读线程可以不断获取锁

  3. 如果没有写锁优先机制,写线程可能永远无法获得执行机会

实现机制详解

非公平模式下的实现

在非公平模式下,当写锁请求出现时:

 final boolean readerShouldBlock() {
     return apparentlyFirstQueuedIsExclusive();
 }
 ​
 // 检查等待队列中的第一个节点是否是写锁请求
 final boolean apparentlyFirstQueuedIsExclusive() {
     Node h, s;
     return (h = head) != null && (s = h.next) != null &&
            !s.isShared() && s.thread != null;
 }

当写锁在队列中等待时,新来的读锁请求会被阻塞,即使当前没有活跃的写锁。

公平模式下的实现

在公平模式下,规则更加严格:

 final boolean readerShouldBlock() {
     return hasQueuedPredecessors();
 }
 ​
 final boolean writerShouldBlock() {
     return hasQueuedPredecessors();
 }

只要队列中有等待的线程(无论是读是写),新来的锁请求都会被加入队列尾部。

场景模拟分析

让我们通过一个时间线来理解这个机制:

时间线: t0 -> t1 -> t2 -> t3 -> t4 -> t5

t0: 读线程R1获取读锁
t1: 读线程R2获取读锁  
t2: 写线程W1请求写锁 → 进入等待队列
t3: 读线程R3请求读锁 → 被阻塞(因为W1在等待)
t4: R1释放读锁
t5: R2释放读锁 → W1获得写锁

如果没有这个机制,在t2-t5期间,新的读线程可能会不断获取读锁,导致W1永远无法获得写锁。

性能考量与最佳实践

选择合适的锁模式

// 非公平锁 - 默认选择,吞吐量更高
ReentrantReadWriteLock nonfairLock = new ReentrantReadWriteLock();

// 公平锁 - 保证线程按申请顺序获取锁,避免饥饿但性能较低
ReentrantReadWriteLock fairLock = new ReentrantReadWriteLock(true);

避免死锁的实践

public class SafeReadWriteUsage {
    private final ReentrantReadWriteLock lock1 = new ReentrantReadWriteLock();
    private final ReentrantReadWriteLock lock2 = new ReentrantReadWriteLock();
    
    // 危险的锁顺序
    public void dangerousMethod() {
        lock1.readLock().lock();
        try {
            lock2.writeLock().lock();  // 可能产生死锁
            try {
                // 业务逻辑
            } finally {
                lock2.writeLock().unlock();
            }
        } finally {
            lock1.readLock().unlock();
        }
    }
    
    // 安全的锁顺序 - 统一获取顺序
    public void safeMethod() {
        lock1.readLock().lock();
        lock2.writeLock().lock();
        try {
            // 业务逻辑
        } finally {
            lock2.writeLock().unlock();
            lock1.readLock().unlock();
        }
    }
}

性能监控与调试

public class MonitoredReadWriteLock extends ReentrantReadWriteLock {
    private final AtomicLong readWaitTime = new AtomicLong();
    private final AtomicLong writeWaitTime = new AtomicLong();
    
    @Override
    public Lock readLock() {
        return new MonitoringReadLock(super.readLock());
    }
    
    private class MonitoringReadLock implements Lock {
        private final Lock delegate;
        
        MonitoringReadLock(Lock delegate) {
            this.delegate = delegate;
        }
        
        @Override
        public void lock() {
            long start = System.nanoTime();
            delegate.lock();
            long duration = System.nanoTime() - start;
            readWaitTime.addAndGet(duration);
        }
        // 其他方法委托...
    }
}

实际应用案例

缓存实现

 public class ReadWriteLockCache<K, V> {
     private final Map<K, V> cache = new HashMap<>();
     private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
     private final Lock readLock = lock.readLock();
     private final Lock writeLock = lock.writeLock();
     
     public V get(K key) {
         readLock.lock();
         try {
             return cache.get(key);
         } finally {
             readLock.unlock();
         }
     }
     
     public void put(K key, V value) {
         writeLock.lock();
         try {
             cache.put(key, value);
         } finally {
             writeLock.unlock();
         }
     }
     
     public V computeIfAbsent(K key, Function<K, V> mappingFunction) {
         V value = get(key);
         if (value == null) {
             writeLock.lock();
             try {
                 // 双重检查
                 value = cache.get(key);
                 if (value == null) {
                     value = mappingFunction.apply(key);
                     cache.put(key, value);
                 }
             } finally {
                 writeLock.unlock();
             }
         }
         return value;
     }
 }

总结

ReentrantReadWriteLock通过精巧的状态设计和线程调度策略,在保证数据一致性的同时,显著提升了读多写少场景下的系统吞吐量。写锁优先机制有效防止了写线程饥饿问题,体现了并发设计中公平性与效率的平衡。

在实际应用中,我们需要根据具体的业务场景和性能要求,合理选择锁模式,并遵循最佳实践来避免潜在的死锁和性能问题。理解其内部机制不仅有助于正确使用该工具,更能提升我们对并发编程本质的认识。


图:ReentrantReadWriteLock锁获取基本流程

图:写锁优先机制防止写线程饥饿


往期免费源码对应视频:

免费获取--SpringBoot+Vue宠物商城网站系统

🥂(❁´◡`❁)您的点赞👍➕评论📝➕收藏⭐➕关注👀是作者创作的最大动力🤞

💖📕🎉🔥 支持我:点赞👍+收藏⭐️+留言📝+关注👀欢迎留言讨论

🔥🔥🔥(源码 + 调试运行 + 问题答疑)

🔥🔥🔥  有兴趣可以联系我

💖学习知识需费心,
📕整理归纳更费神。
🎉源码免费人人喜,
🔥码农福利等你领!

💖常来我家多看看,
📕网址:扣棣编程
🎉感谢支持常陪伴,
🔥点赞关注别忘记!

💖山高路远坑又深,
📕大军纵横任驰奔,
🎉谁敢横刀立马行?
🔥唯有点赞+关注成!

⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇点击此处获取源码⬇⬇⬇⬇⬇⬇⬇⬇⬇

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值