深度解析:读写锁原理

对于多个线程共享同一个资源的时候,多个线程同时对共享资源做读操作是不会发生线程安全性问题的,但是一旦有一个线程对共享数据做写操作其他的线程再来读写共享资源的话,就会发生数据安全性问题,所以出现了读写锁ReentrantReadWriteLock。读写锁允许多个线程同时获取读锁,但有一个线程获取写锁之后其他线程都会进入等待队列进行等待。

读写锁的写锁是一把独占锁,它与ReentrantLock的原理十分相似,可以参靠https://blog.csdn.net/qq_37685457/article/details/89704124,这里就不解释写锁了,来说说读锁。

因为读写锁需要用一个int类型的值来保存读锁和写锁两把锁的数量,所以要将int类型的值进行拆分存储,int类型占4个字节,每个字节占8位,所以一个int类型的变量占32位,将高16用来保存读锁的数量,低16位保存写锁的数量。

读写锁的读锁原理:如果有线程想申请读锁的话,首先会判断写锁是否被持有,如果写锁被持有且当前线程并不是持有写锁的线程,那么就会返回-1,获取锁失败,进入到等待队列等待。如果写锁未被线程所持有或者当前线程和持有写锁的线程是同一线程的话就会开始获取读锁。线程首先会判断读锁的数量是否超过65535个,如果没超过就CAS修改state变量的高16位的值,也就是将state的值+1,如果这个步骤失败的话它会循环这个操作,直到成功为止。CAS修改成功之后,代表读锁获取成功,会判断一下当前线程是否是第一次读线程,如果是,就设置第一次多线程和第一次读计数器(为了性能和可重入)。如果不是第一次获取读锁,判断一下是否是与第一次读线程相同,如果与第一次读线程是同一线程就将第一次读计数器+1。如果也不是第一次读线程,判断一下是否是最后一次读线程,如果是就将最后一次读计数器+1。如果都不是,就新建一个计数器,设置最后一次读线程为自己本身线程,然后刷新它的读计数器。

读锁源码:

protected final int11 tryAcquireShared(int unused) {

    Thread current = Thread.currentThread();
    int c = getState();
    // exclusiveCount(c) != 0 ---》 用 state & 65535 得到低 16 位的值。如果不是0,说明写锁别持有了。
    // getExclusiveOwnerThread() != current----> 不是当前线程
    // 如果写锁被霸占了,且持有线程不是当前线程,返回 false,加入队列。获取写锁失败。
    // 反之,如果持有写锁的是当前线程,就可以继续获取读锁了。
    if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current)
        // 获取锁失败
        return -1;
    // 如果写锁没有被霸占,则将高16位移到低16位。
    int r = sharedCount(c);// c >>> 16
    // !readerShouldBlock() 和写锁的逻辑一样(根据公平与否策略和队列是否含有等待节点)
    // 不能大于 65535,且 CAS 修改成功
    if (!readerShouldBlock() && r < 65535 && compareAndSetState(c, c + 65536)) {
        // 如果读锁是空闲的, 获取锁成功。
        if (r == 0) {
            // 将当前线程设置为第一个读锁线程
            firstReader = current;
            // 计数器为1
            firstReaderHoldCount = 1;

        }// 如果读锁不是空闲的,且第一个读线程是当前线程。获取锁成功。
         else if (firstReader == current) {// 
            // 将计数器加一
            firstReaderHoldCount++;
        } else {// 如果不是第一个线程,获取锁成功。
            // cachedHoldCounter 代表的是最后一个获取读锁的线程的计数器。
            HoldCounter rh = cachedHoldCounter;
            // 如果最后一个线程计数器是 null 或者不是当前线程,那么就新建一个 HoldCounter 对象
            if (rh == null || rh.tid != getThreadId(current))
                // 给当前线程新建一个 HoldCounter ------>详见下图get方法
                cachedHoldCounter = rh = readHolds.get();
            // 如果不是 null,且 count 是 0,就将上个线程的 HoldCounter 覆盖本地的。
            else if (rh.count == 0)
                readHolds.set(rh);
            // 对 count 加一
            rh.count++;
        }
        return 1;
    }
    // 死循环获取读锁。包含锁降级策略。
    return fullTryAcquireShared(current);
}

 

源码还是比较多,附个图吧,简单明了 

读写锁还存在一个锁降级的概念,就是在释放写锁之前可以将锁降级为读锁,但是读锁不可升级为写锁。锁降级的目的一直都存在争议,但其实锁降级就是一种特殊的可重入操作。存在一直情况,如果当前线程已经持有写锁,但如果当前线程再来获取读锁的话应该是允许的,但如果写锁不释放的话,就算是本身线程来获取读锁也要进行等待,这样是不合理的,所以说它允许锁降级为读锁,这样就可以不用等待直接获取读锁了,也是为了效率的一种体现。

  • 7
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值