• JUC锁: ReentrantReadWriteLock详解


    带着BAT大厂的面试问题去理解

    • 为了有了ReentrantLock还需要ReentrantReadWriteLock?
    • ReentrantReadWriteLock底层实现原理?
    • ReentrantReadWriteLock底层读写状态如何设计的? 高16位为读锁,低16位为写锁
    • 读锁和写锁的最大数量是多少?
    • 本地线程计数器ThreadLocalHoldCounter是用来做什么的?
    • 缓存计数器HoldCounter是用来做什么的?
    • 写锁的获取与释放是怎么实现的?
    • 读锁的获取与释放是怎么实现的?
    • RentrantReadWriteLock为什么不支持锁升级?
    • 什么是锁的升降级? RentrantReadWriteLock为什么不支持锁升级?

    ReentrantReadWriteLock简介

    ReentrantReadWriteLock数据结构

    ReentrantReadWriteLock底层是基于ReentrantLock和AbstractQueuedSynchronizer来实现的,所以,ReentrantReadWriteLock的数据结构也依托于AQS的数据结构。

    ReentrantReadWriteLock源码分析

    类的继承关系

    public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {}
    

    类的内部类

    ReentrantReadWriteLock有五个内部类,五个内部类之间也是相互关联的。内部类的关系如下图所示。

    说明: 如上图所示,Sync继承自AQS、NonfairSync继承自Sync类、FairSync继承自Sync类;ReadLock实现了Lock接口、WriteLock也实现了Lock接口。

    类的构造函数

    • ReentrantReadWriteLock()型构造函数
    1. public ReentrantReadWriteLock() {
    2. this(false);
    3. }

    说明: 此构造函数会调用另外一个有参构造函数。

    1. public ReentrantReadWriteLock(boolean fair) {
    2. // 公平策略或者是非公平策略
    3. sync = fair ? new FairSync() : new NonfairSync();
    4. // 读锁
    5. readerLock = new ReadLock(this);
    6. // 写锁
    7. writerLock = new WriteLock(this);
    8. }

    说明: 可以指定设置公平策略或者非公平策略,并且该构造函数中生成了读锁与写锁两个对象。

    ReentrantReadWriteLock示例

    下面给出了一个使用ReentrantReadWriteLock的示例,源代码如下。

    1. import java.util.concurrent.locks.ReentrantReadWriteLock;
    2. class ReadThread extends Thread {
    3. private ReentrantReadWriteLock rrwLock;
    4. public ReadThread(String name, ReentrantReadWriteLock rrwLock) {
    5. super(name);
    6. this.rrwLock = rrwLock;
    7. }
    8. public void run() {
    9. System.out.println(Thread.currentThread().getName() + " trying to lock");
    10. try {
    11. rrwLock.readLock().lock();
    12. System.out.println(Thread.currentThread().getName() + " lock successfully");
    13. Thread.sleep(5000);
    14. } catch (InterruptedException e) {
    15. e.printStackTrace();
    16. } finally {
    17. rrwLock.readLock().unlock();
    18. System.out.println(Thread.currentThread().getName() + " unlock successfully");
    19. }
    20. }
    21. }
    22. class WriteThread extends Thread {
    23. private ReentrantReadWriteLock rrwLock;
    24. public WriteThread(String name, ReentrantReadWriteLock rrwLock) {
    25. super(name);
    26. this.rrwLock = rrwLock;
    27. }
    28. public void run() {
    29. System.out.println(Thread.currentThread().getName() + " trying to lock");
    30. try {
    31. rrwLock.writeLock().lock();
    32. System.out.println(Thread.currentThread().getName() + " lock successfully");
    33. } finally {
    34. rrwLock.writeLock().unlock();
    35. System.out.println(Thread.currentThread().getName() + " unlock successfully");
    36. }
    37. }
    38. }
    39. public class ReentrantReadWriteLockDemo {
    40. public static void main(String[] args) {
    41. ReentrantReadWriteLock rrwLock = new ReentrantReadWriteLock();
    42. ReadThread rt1 = new ReadThread("rt1", rrwLock);
    43. ReadThread rt2 = new ReadThread("rt2", rrwLock);
    44. WriteThread wt1 = new WriteThread("wt1", rrwLock);
    45. rt1.start();
    46. rt2.start();
    47. wt1.start();
    48. }
    49. }

    运行结果(某一次):

    1. rt1 trying to lock
    2. rt2 trying to lock
    3. wt1 trying to lock
    4. rt1 lock successfully
    5. rt2 lock successfully
    6. rt1 unlock successfully
    7. rt2 unlock successfully
    8. wt1 lock successfully
    9. wt1 unlock successfully

    (api方法较为简单,这里不做过多分析。总而言之,就是可共享读,不可共享写,读写互斥读读共享)

    什么是锁升降级?

    接下来看一个锁降级的示例。因为数据不常变化,所以多个线程可以并发地进行数据处理,当数据变更后,如果当前线程感知到数据变化,则进行数据的准备工作,同时其他处理线程被阻塞,直到当前线程完成数据的准备工作,如代码如下所示:

    1. public void processData() {
    2. readLock.lock();
    3. if (!update) {
    4. // 必须先释放读锁
    5. readLock.unlock();
    6. // 锁降级从写锁获取到开始
    7. writeLock.lock();
    8. try {
    9. if (!update) {
    10. // 准备数据的流程(略)
    11. update = true;
    12. }
    13. readLock.lock();
    14. } finally {
    15. writeLock.unlock();
    16. }
    17. // 锁降级完成,写锁降级为读锁
    18. }
    19. try {
    20. // 使用数据的流程(略)
    21. } finally {
    22. readLock.unlock();
    23. }
    24. }

    锁降级中读锁的获取是否必要呢? 答案是必要的。主要是为了保证数据的可见性,如果当前线程不获取读锁而是直接释放写锁,假设此刻另一个线程(记作线程T)获取了写锁并修改了数据,那么当前线程无法感知线程T的数据更新。如果当前线程获取读锁,即遵循锁降级的步骤,则线程T将会被阻塞,直到当前线程使用数据并释放读锁之后,线程T才能获取写锁进行数据更新。

    锁饥饿问题

      • ReentrantReadWriteLock(boolean)型构造函数
  • 相关阅读:
    什么是Integer128陷阱
    动手写prometheus的exporter-04-Summary(摘要)
    labelimg标注的VOC格式标签xml文件和yolo格式标签txt文件相互转换
    鼠标参数以及选购DPI和报告率
    ajax笔记五
    CLIP与DINOv2的图像相似度对比
    C++初阶(五)类和对象
    golang 多环境配置切换
    Fluent Facede Pattern(外观模式)
    Linux之(13)shell(5)sed进阶
  • 原文地址:https://blog.csdn.net/weixin_63566550/article/details/126293453