(六)JDK源码分析之可重入读写锁ReentrantReadWriteLock

  • 概述
    可重入读写锁,其实就是在重入锁基础上区分了一下读写锁.读写锁也称为共享锁,也就是说可以多个线程同时获取锁,这样大大提高了系统的吞吐量. 读写锁中 读锁和读锁是共享的,写锁和读锁写锁都是互斥的.

  • 锁的状态如何区分读写锁
    我们经过前面分析,知道在同步器中,用了一个int型来表示一个状态,当这个int值不为0时,说明这个时候锁是被获取了的,这个时候其他线程只能自旋等待, 当然到了读写锁也不例外,因为底层用的都是同一个同步器AbstractQueuedSynchronizer,所以依然是使用一个整形来表示锁的状态,那么如何来分别表示读锁和写锁的状态,看下图
    在这里插入图片描述
    如图,因为一个int类型是四个字节,一共是32位,这个时候用的策略就是使用高16位表示读锁的重入次数,用低16位表示写锁重入次数,这样就可以分别表示出读写锁的状态了.比如上面读锁的状态是2,写锁的状态是3,但是这个带来的问题是,状态的值缩短了一半
    在这里插入图片描述
    在读写锁实现中使用了位运算来分别计算读写锁当前的状态值
    a.sharedCount函数返回读锁的当前状态值
    b.exclusiveCount函数返回写锁的当前状态值

  • 读锁源码分析
    在这里插入图片描述
    在这里插入图片描述
    和重入锁设计一样,控制获取锁的主流程在同步器中,具体实现在子类中,主要分为两步:
    1.tryAcquireShared获取读锁
    在这里插入图片描述在获取读锁做的事情主要流程如下:
    a. 如果别的线程已经获取写锁,那么直接获取读锁失败返回-1, 如果是获取的写线程是当前线程,那么可以继续获取读锁
    b.判断读锁是不是应该被阻塞,这个根据非公平锁和公平锁实现是不一样的; 在非公平中,因为是非公平的,不考虑线程等待问题,直接当前在执行中的线程优先,但是为了不导致写饥饿,如果在队列中有写锁排队,那么当前获取读锁阻塞; 在公平锁实现中,是根据等待时间优先来的,总是判断队列有没有等待的线程,如果有,那么阻塞当前线程,否则直接获取读锁
    c.使用ThreadLocal保存当前线程获取读锁的次数, 第一个线程有单独变量保存firstReader,firstReaderHoldCount
    2.doAcquireShared获取读锁失败,加入到队列中
    在这里插入图片描述

  • 写锁源码分析
    在这里插入图片描述
    同样的道理获取写锁也是直接使用同步器的模版方法,也是先尝试获取锁,如果获取失败,那么加入队列.上图为第一步,第二部加入队列实现都一样就不累赘了
    1.如果读锁存在,那么获取写锁失败;如果写锁存在,不是当前线程获取的,那么获取锁失败
    2.如果存在写锁,并且就是当前线程,那么重入特性满足
    3.writerShouldBlock方法,对于非公平和公平实现是不一样的,具体和读锁一样:
    a.公平锁,查看队列有没有,有那么阻塞,加入到任务队列,否则尝试获取锁
    b.非公平锁.直接尝试获取锁

  • 总结
    通过上面分析,读写锁的特性:
    a.读锁: 如果已经存在写锁,那么获取读锁失败,如果获取了写锁的线程就是当前线程,那么可以继续获取读锁
    b 写锁: 如果当前存在读锁,那么获取写锁失败,如果获取了读锁的线程是当前线程,那么获取写锁仍然失败.

发布了65 篇原创文章 · 获赞 11 · 访问量 7139

猜你喜欢

转载自blog.csdn.net/weixin_38312719/article/details/103534797