readerShouldBlock

1 顾名思义

其实java很多功能,都是在原基础功能上做一层变换。读写锁就是在互斥锁Reentrantlock的逻辑基础上,对状态码的使用做了很多改进,这个改进得益于二进制在计算机中的计算方式。
一句话描述读写锁:
读读共享、读写互斥、写写互斥

2.状态码的使用

ReentrantReadWriteLock和Reentrantlock的状态码都是来自AQS的state属性,该属性定义为int型,也就是32位二进制数据。在Reentrantlock并没有对state进行特殊使用,然而在ReentrantReadWriteLock中,将state分为低16位和高16位来使用。低16位主要用于写锁的操作,高16位用于读锁的操作。
举例说明:

  1. 一个线程占有写锁,state的二进制为:0000 0000 0000 0000 0000 0000 0000 0001
  2. 一个线程占有写锁,再次重入写锁,state的二进制为:0000 0000 0000 0000 0000 0000 0000 0002
  3. 一个线程占有读锁,state的二进制为:0000 0000 0000 0001 0000 0000 0000 0000
  4. 一个线程占有读锁,再次重入读锁,state的二进制为:0000 0000 0000 0002 0000 0000 0000 0000
  5. 一个线程先占有写锁,再获取读锁(锁降级),state的二进制为:0000 0000 0000 0001 0000 0000 0000 0001

3.直入源码

3.1 构造方法

    public static void main(String[] args) {
        final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    }
    public ReentrantReadWriteLock() {
        //使用默认的有参构造器,传入false
        this(false);
    }
    //和Reentrantlock一样,默认是非公平锁
    public ReentrantReadWriteLock(boolean fair) {
        //AQS实例化
        sync = fair ? new FairSync() : new NonfairSync();
        //读锁实例化
        readerLock = new ReadLock(this);
        //写锁实例化
        writerLock = new WriteLock(this);
    }

3.2 读写锁对象获取

    public static void main(String[] args) {
        final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
        //必须先获取写锁对象才能具体操作
        lock.writeLock();
        //必须先获取读锁对象才能具体操作
        lock.readLock();
    }
    //获取写锁对象
    public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
    //获取读锁对象
    public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }

3.2 内部类读锁ReadLock

在这里插入图片描述

3.2.1 构造器和私有属性

 public static class ReadLock implements Lock, java.io.Serializable {
        private static final long serialVersionUID = -5992448646407690164L;
        //AQS对象,可想而知,该类仅仅封装最外层的方法,实际具体实现还是走的Sync的方法
        private final Sync sync;
        protected ReadLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }
        ...

3.2.2 lock方法

1.lock()

        public void lock() {
        	//走的内部类Sync的方法
            sync.acquireShared(1);
        }

2.acquireShared

  public final void acquireShared(int arg) {
        //尝试获取读锁
        if (tryAcquireShared(arg) < 0)
            //获取失败,正式开始获取读锁
            doAcquireShared(arg);
    	}

3.tryAcquireShared(int unused)
我们需要先了解Sync一些属性的定义和state的一些计算方法:

    abstract static class Sync extends AbstractQueuedSynchronizer {
      
           static final int SHARED_SHIFT   = 16;
           // 由于读锁是操作高16位部分,所以想要让读锁状态值+1,就必须让state加2的16次幂,也就是加0000 0000 0000 0001 0000 0000 0000 0000,用代码表示2的16次幂,就是1<<16,就是左移16位。
           static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
           // 写锁的可重入的最大次数,由于写锁操作低16位,那么低16位最大值不就是2的16次幂-1。也就是0000 0000 0000 0000 1111 1111 1111 1111
           static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
           // 可以看出和MAX_COUNT一样的值,实际上是用来做运算的,从state中取出低16位值
           static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
           // >>>表示无符号右移16位,即取出state高16值,用于判断读锁是否占有,大于1表示所有读线程获取读锁的次数
       	   static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
           // 通过将c与EXCLUSIVE_MASK 做与运算,取出state低16位值,用于判断写锁是否占有,大于1表示某个线程写锁重入的次数
       	   static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
    }
protected final int tryAcquireShared(int unused) {
    // 获取当前线程对象
    Thread current = Thread.currentThread();
    // 获取锁状态
    int c = getState();
    //如果低16位不等于0,也就是写锁被占有。且独占锁不是当前线程则返回失败,为什么还要判断这个?因为存在锁降级(已经获取写锁的线程,还没释放写锁,可以再次获取读锁)
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    // 高16位值
    int r = sharedCount(c);
     // readerShouldBlock很重要,请看下面详细解读
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {
        //r == 0,表示第一个读锁线程,第一个读锁firstRead是不会加入到readHolds中
        if (r == 0) { // 读锁数量为0
            // 设置第一个读线程为当前线程
            firstReader = current;
            // 首次读锁重入次数为1
            firstReaderHoldCount = 1;
        } else if (firstReader == current) { // 当前线程为第一个读线程,表示第一个读锁线程重入
            // 首次读锁重入次数+1
            firstReaderHoldCount++;
        } else { // 读锁数量不为0并且不为当前线程
            // 获取计数器
            HoldCounter rh = cachedHoldCounter;
            // 计数器为空或者计数器的tid不为当前正在运行的线程的tid
            if (rh == null || rh.tid != getThreadId(current))
                // 获取当前线程对应的计数器
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0) // 计数为0
                //加入到readHolds中
                readHolds.set(rh);
            //计数+1
            rh.count++;
        }
        return 1;
    }
    return fullTryAcquireShared(current);
}

4.readerShouldBlock(难)

final boolean readerShouldBlock() {
     return apparentlyFirstQueuedIsExclusive();
}
//翻译下方法名,apparently  First Queued is Exclusive:确认队列第一个节点是否独占?
//第一个节点是哪个?
//独占?不就是写锁吗?
//是不是看方法名就大概知道这个方法干嘛的?
final boolean apparentlyFirstQueuedIsExclusive() {
        Node h, s;
        return 
            //头结点不允许为空,也就是阻塞队列存在
            (h = head) != null &&
            //头节点下一个节点必须存在
            (s = h.next)  != null &&
            //下一个节点不能共享,也就是写节点嘛
            !s.isShared()         &&
            //下一个节点的线程对象不允许为空
            s.thread != null;
    }
  1. 这个方法很重要,没有这个方法读写锁在现实使用中,会出现业务性的写死锁?why?现实业务需求中,进行读操作的次数远远大于写操作,也就是读操作比写操作频繁的多。假如某个业务线程获取到读锁,然后其他大批量读业务不间断涌入,由于读读共享,导致读锁一直被读线程占有。假如一天?一周?甚至一个月?一直不间断有读业务产生。那么些写业务还用干吗?等一天?等一周?等一个月?直到等到一个机会,没有一个线程占有读锁,才能获取到锁。
  2. 很显然,这会很容易导致系统写业务崩盘,那么读写锁的设计意义就失去了。
  3. 于是有没有解决方案?当然有,如果发现阻塞队列有写线程在排队,且排队在第一个。那么,新来的读线程统统靠边站,给我老老实实排队去,这不就解决了吗?看4、5详细描述。
  4. 在没有哪个线程独占写锁的情况下,对新来的读线程new Reader(注意是新来的,不是新来的会怎么样?后面会有说到)想要获取读锁的家伙(线程),在获取读锁之前做一些判断。如果阻塞队列是空的,OK你可以获取;如果阻塞队列不是空的,分两种情况。一,如果第一个节点是写节点,No你不能获取读锁,给我排队去(会阻塞)。二,如果第一个节点是读节点,OK你可以获取。
  5. 在没有哪个线程独占写锁的情况下,对与已经获取到读锁的线程old Reader,OK你可以再次重入读锁。
  6. readerShouldBlock返回的true不应该立马认为线程应该阻塞吗?当然不是,步骤5立马说了,有可能是old Reader想重入读锁。readerShouldBlock方法是无法区分是new Reader还是old Reader的。它仅仅判断阻塞队列第一个节点如果是写节点,就返回true。
  7. 这就是为什么最后,还要来一个fullTryAcquireShared方法,该方法就是用于区分new Reader和old Reader
    在这里插入图片描述

在这里插入图片描述5.fullTryAcquireShared
…等待更新

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值