并发(5)——ReentrantReadWriteLock源码解析

使用ReentrantReadWriteLock的好处

  • 互斥锁一次只允许一个线程访问共享数据,哪怕进行的是只读操作;
  • 读写锁允许对共享数据进行更高级别的并发访问;
  • 对于写操作,一次只有一个线程(write线程)可以修改共享数据,对于读操作,允许任意数量的线程同时进行读取。

与互斥锁相比,使用读写锁能够提升性能则取决于读写操作期间读取数据相对于修改数据的频率,以及数据的争用——即在同一时间试图对该数据执行读取或者写入操作的线程数。

需要思考的问题

  • 【问题一】AQS只有一个状态,那么如何表示 多个读锁 与 单个写锁 呢?

  • 【问题二】ReentrantLock里,状态值表示重入计数,现在如何在AQS里面表示每个读锁、写锁的重入次数呢?

  • 【问题三】如何实现读锁和写锁的公平性呢?

  • 【答案一】一个状态是没法既表示读锁,又表示写锁的,不够用,所以就将一个状态变量分成两份用,一个变量一共32位,高16位表示读锁,低16位表示写锁。因为写锁只有一个,所以写锁的重入计数也解决了。

  • 【答案二】由于读锁可以同时有多个,肯定不能再通过解决问题一的方法来解决问题二了,这里我们使用ThreadLocal,可以把线程重入读锁的次数作为值存放到ThreadLocal

ReentrantReadWriteLock类的结构

在这里插入图片描述

在这里插入图片描述

ReadLock源码 之 读锁

首先我们看一下 高16位读锁 和 低16位写锁 是如何进行分配的

在这里插入图片描述

创建读锁对象的操作

在这里插入图片描述

在这里插入图片描述

获取读锁对象,即将读锁锁住

在这里插入图片描述

该方法调用的是ReentrantReadWriteLock类的内部类Sync中的acquireShared()方法,但是在Sync中没有找到该方法,所以我们找Sync的父类AQS中的该方法。

在这里插入图片描述

我们可以看到该方法调用了两个方法,一个是tryAcquireShared,另一个是doAcquireShared,其中tryAcquireShared是由AQS的实现类来实现的。接下来,我们看看tryAcquireShared的实现。

在这里插入图片描述

在这里插入图片描述

针对获取readLock的线程的获取次数需要分3种情况

  • 线程的tid及获取次数count存放在 HoldCounter里面,最后放在ThreadLocal中.

  • cachedHoldCounter获取存入的信息

    • 注意:这里不是有一个ThreadLocl,为什么还需要使用cacheHolderCounter呢?
    • 因为:多数情况下,在进行线程 acquire readLock后不久就会进行相应的release,而从cachedHoldCounter获取,省去了从ThreadLocallookup的操作(其实就是节省了资源,ThreadLocal中查找需要遍历数组)
  • firstReader firstReaderHoldCount 这两个属性是用来记录第一次获取锁的线程,及重入的次数。当当前线程进行释放readLock操作后,firstReader会被置空,当再有新的线程获取readLock后,firstReader就会被赋值为新的线程。

总结一下上面以共享方式获取锁的逻辑:

在这里插入图片描述

图片来自:并发编程网

fullTryAcquireShared方法

        final int fullTryAcquireShared(Thread current) {
            /*
             * 这段代码与tryAcquireShared中的代码有部分重复,但是整体更简单
             */
            HoldCounter rh = null;
            // 死循环
            for (;;) {
                int c = getState();
                // 如果存在写锁
                if (exclusiveCount(c) != 0) {
                    // 如果存在写锁,并且不是当前线程,获取锁失败,反之,如果持有写锁的是当前线程,那么就会进入下面的逻辑。
                    // 反之,如果存在写锁,但持有写锁的是当前线程,那么就继续尝试获取读锁。
                    if (getExclusiveOwnerThread() != current)
                        return -1;
                    // else we hold the exclusive lock; blocking here
                    // would cause deadlock.
                    // 如果不存在写锁,且可以获取读锁。
                } else if (readerShouldBlock()) {
                    // 第一个读线程是当前线程
                    if (firstReader == current) {
                        // 如果不是当前线程
                    } else {
                        if (rh == null) {
                            rh = cachedHoldCounter;
                            if (rh == null || rh.tid != getThreadId(current)) {
                                // 从 ThreadLocal 中取出计数器
                                rh = readHolds.get();
                                if (rh.count == 0)
                                    readHolds.remove();
                            }
                        }
                        if (rh.count == 0)
                            return -1;
                    }
                }
                // 如果读锁次数达到65535,则抛出异常
                if (sharedCount(c) == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // 尝试对state 加 65536,也就是设置读锁,实际就是对高 16 位加 1
                if (compareAndSetState(c, c + SHARED_UNIT)) {
                    // 如果读锁是空闲的
                    if (sharedCount(c) == 0) {
                        // 设置第一个读锁
                        firstReader = current;
                        // 计数器为1
                        firstReaderHoldCount = 1;
                        // 如果读锁不是空闲的,即有其他线程获取了读锁,则查看第一个线程是否是当前线程
                    } else if (firstReader == current) {
                        // 如果当前线程是第一个获取读锁的线程,则更新计数器
                        firstReaderHoldCount++;
                    } else {
                        if (rh == null)
                            rh = cachedHoldCounter;
                        // 如果最后一个度计数器所属线程不是当前线程,则自己创建一个
                        if (rh == null || rh.tid != getThreadId(current))
                            rh = readHolds.get();
                        else if (rh.count == 0)
                            readHolds.set(rh);
                        // 对计数器+1
                        rh.count++;
                        // 更新缓存计数器
                        cachedHoldCounter = rh; // cache for release
                    }
                    return 1;
                }
            }
        }

解释几个概念:

  • firstReader:是获取读锁的第一个线程。如果只有一个线程获取读锁,很明显,使用这样一个变量速度更快。
  • firstReaderHoldCount:是firstReader的计数器。
  • cachedHoldCounter:是最后一个获取到读锁的线程计数器,每当有新的线程获取到读锁,这个变量都会更新。这个变量的目的是:当最后一个获取读锁的线程重复获取读锁,或者释放读锁,就会直接使用这个变量,速度更快,相当于缓存。
读锁的释放

在这里插入图片描述

直接调用ReentrantReadWriteLock的内部类Sync中的releaseShared方法,该方法实际调用的是AQS中的releaseShared方法。

在这里插入图片描述

这个方法中调用了两个方法,一个是tryReleaseShared方法,另一个是doReleaseShared方法。

在AQS中的tryReleaseShared方法是直接抛出异常,所以需要ReentrantReadWriteLock的内部类Sync自己实现该方法。因此,在Sync中该方法实现过程如下:

在这里插入图片描述

读锁释放的过程:

  • 【步骤一】如果当前线程是第一个持有读锁的线程,则只需要操作firstReaderHoldCount减1,如果不是,进入步骤二;
  • 【步骤二】获取到缓存计数器(最后一个线程的计数器),如果匹配到当前线程,就减少一。如果不匹配,则进入第三步;
  • 【步骤三】获取当前线程自己的计数器(由于每个线程都会多次获取到锁,所以,每个线程必须保存自己的计数器);
  • 【步骤四】做减1的操作
  • 【步骤五】死循环修改 state 变量。
ReadLock源码 之 写锁

写锁的获取

在这里插入图片描述

获取写锁,这里调用了Sync类的acquire方法,我们之前也分析过了,在Sync类中没有该方法,所以实际调用的是AQS中的acquire方法。

在这里插入图片描述

AQS中的这个方法在分析ReentLock类源码的时候分析过了,这里就不再赘述了。首先这个类里面调用了两个方法,一个是tryAcquire,另一个是acquireQueued方法,我们先来分析tryAcquire方法,这个方法是调用Sync中的tryAcquire方法。

在这里插入图片描述

写锁的释放

在这里插入图片描述

该方法调用了ReentrantReadWriteLock内部类Sync中的release方法,但是该类中没有该方法,所以其实调用的是AQS中的release方法。

在这里插入图片描述

在这里插入图片描述

参考并感谢

[1] 并发编程网——莫那·鲁道

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值