ReentrantReadWriteLock源码简析

在讲ReentrantReadWriteLock读写锁之前,一个没法绕过的东西就是AQS(AbstractQueuedSynchronizer)抽象队列同步器,并发包下的很多组件都是基于AQS来实现的。没有AQS这个地基,就没有ReentrantLock和ReentrantReadWriteLock等这些上层建筑

AQS中有三个重要的组成部分,分别是state、exclusiveOwnerThread和Node链表
下面分别来解释一下这三个部分分别有什么用:

  1. state:拿ReentrantLock来举例,这是一个可重入锁,所以可以多次加锁,state变量是一个int类型,其实就是记录的是加锁的状态,也可以说是重入加锁的次数,底层是使用CAS操作来修改state的
  2. exclusiveOwnerThread:这个变量代表的是当前的加锁线程
  3. Node链表:其实这充当的是一个等待队列,当一个线程加了锁之后,其他线程就只能阻塞等待了,而这些阻塞等待的线程就会组成一个Node链表,Node里面就是挂起的线程

来模拟一下两个线程加锁的场景,线程1和线程2同时来加锁,线程1成功获取到了锁,将state改为了1,并将exclusiveOwnerThread设置为了线程1,线程2加锁时,尝试CAS操作state时发现已经不是0了,同时自己也不是加锁的线程,发现线程1已经获取到了锁,所以自己只能加入到Node链表中,等待线程1释放锁,自己才能再次尝试加锁
线程1释放锁,state减1,然后将exclusiveOwnerThread设置为null(只有当state = 0时才会将当前加锁线程置为null),此时就算完全释放了锁,然后线程1会去等待队列的队头唤醒线程2,接着线程2就会再次尝试加锁
这里有一个点需要注意一下,如果是非公平锁的话,在线程2被唤醒然后重新加锁的期间,如果这时来了个线程3,不管三七二十一先加锁成功的话,那么线程2是无法加锁成功的,只能继续等待获取锁,如果线程2一直被其他的线程抢占锁的话,就可能会造成线程的饥饿

简单来说,AQS大致就是这样的原理(如果讲源码的话,我也讲不清,你也看不懂),画个图理解一下
AQS加锁流程
讲完了AQS的原理之后,再理解ReentrantReadWriteLock就简单了,ReentrantReadWriteLock底层也是基于AQS来实现的,我们知道,读写锁的特性是可以加读锁和写锁,读锁和写锁是互斥的,不同的线程不能同时加这两种锁,但是读锁和读锁不是互斥的,不同的线程都可以加读锁,在读多写少的场景下,可以有效提升并发能力。那么问题来了,基于AQS来做的话,要怎么同时来实现读锁和写锁这两种锁呢?

上面讲的AQS里面的state是用来记录加锁的状态的,那么如何用一个变量来代表两种锁?
state是一个int类型的值,是32位的,所以在读写锁中,高16位被用来表示读锁,低16位被用来表示写锁
ReentrantReadWriteLock和ReentrantLock的不同就在于读写锁有两把锁,这两把锁之间是互斥的,我们就来分析一下源码中读写锁的互斥关系

加读锁源码

// 方法返回值小于0,则加读锁失败
protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    // state,加锁状态
    int c = getState();
   	// exclusiveCount(c)代表的是加写锁的数量,如果不等于0,说明已经有线程加了写锁
   	// getExclusiveOwnerThread() != current 说明加锁的线程不是当前线程,也就是说加锁的线程是其他的线程
   	// 两个条件合并来看就是如果已经有其他的线程加了写锁,那么就不允许你再加读锁了,这样就实现了读写锁的互斥
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    //后面的代码都不是我们所关注的,所以省略了
}

加写锁源码

// 方法返回值false,则加写锁失败
protected final boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();
    // state,加锁状态
    int c = getState();
    // 加写锁的数量
    int w = exclusiveCount(c);
    // 判断state的值是否不等于0,如果不等于0的话,那么一定是加了读锁或者写锁,读锁和写锁分别占有了高低16位,只要加了锁,state就一定不是0
    if (c != 0) {
    	// 这里的if判断里面是 || ,所有只有两个条件都为false,才会跳过这段逻辑,一个个条件来分析一下
        // 首先是如果w等于0的话,说明没有加过写锁,根据上面的先决条件,可以推断出肯定有人加了读锁,此时w == 0为true,直接返回false
        // 接下来是加锁线程不是当前线程,那么在前面的 w == 0条件为false的情况下,也就是没人加读锁,此时可以推断出有人加了写锁
        // 但是如果这个写锁不是当前线程加的,即current != getExclusiveOwnerThread()返回true,还是会进入if判断内的逻辑,加写锁失败
        // 通过这两个判断发现,加写锁的时候,如果已经有人加了读锁,或者别的线程加了写锁,都会导致加写锁失败
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
 		
        // 省略无关代码
    }
    // 省略无关代码
}

通过源码的分析,我们发现了在其他线程已经加了写锁的情况下,是无法再加读锁的;
在其他线程加了写锁的时候,自己也是无法加写锁的;
在自己或其他线程加了读锁的情况下,也是无法再加写锁的

综合各种情况,ReentrantReadWriteLock读写锁的特性就是不同线程加锁时,写锁和写锁互斥,读锁和写锁互斥,只有读锁和读锁是不互斥的,下图展示了读写锁的互斥关系
读写锁互斥关系

参考:https://mp.weixin.qq.com/s/PAn5oTlvVmjMepmCRdBnkQ(AQS推荐看这篇,简单易懂)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值