• 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)型构造函数
  • 相关阅读:
    用于图像处理的高斯滤波器 (LoG) 拉普拉斯
    08.22面向对象的知识点(1)
    用输出倒逼输入
    【毕业设计】基于树莓派的指纹识别与RFID考勤系统 - 嵌入式 单片机 物联网
    贪心算法篇——区间问题
    合并有序链表
    Vue + Flask 实现单页面应用
    【Unity】预制体材质变(Clone)克隆体问题
    聊一聊Java并发运行中的一些安全问题
    科技的成就(三十三)
  • 原文地址:https://blog.csdn.net/weixin_63566550/article/details/126293453