• 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)型构造函数
  • 相关阅读:
    企业实施MES系统的关键点详解
    STM32单片机控制直流电机实现PID闭环控制源码集锦
    LiveNVR监控流媒体Onvif/RTSP功能-支持海康摄像头海康NVR通过EHOME协议ISUP协议接入分发视频流或是转GB28181
    系列文章|云原生时代下微服务架构进阶之路 - Spring Boot
    不知道PDF文件怎么合并?这3个方法值得一用
    C语言练习---【求素数】(一篇带你掌握素数求解)
    Settings 笔记整理
    Python150题day10
    Java设计模式-工厂模式
    消息队列:原理与应用
  • 原文地址:https://blog.csdn.net/weixin_63566550/article/details/126293453