ReentrantReadWriteLock原理分析

2023-11-16

 在介绍ReentrantReadWriteLock读写锁原理之前,先来说下写锁与读锁。方便后续大家的理解。

1、当资源被写锁占用时,此时是不允许去读的。只有当写锁被释后读锁才能去申请资源、

2、当资源没有被写锁占用时,多个线程是可以共享资源。

写锁必须是是单个线程去进行写、当一个线程在对资源进行写操作时、另外的线程必须等待。这时刚好用到了AQS的两种锁模式:共享模式和独占模式。readLock就是基于AQS的是共享模式设计的。writeLock是基础AQS的独占模式进行设计。下面我们对源码进行较深入的分析。直接看代码的注释。

ReentrantReadWriteLock 实现了ReadWriteLock接口。

 //接口就两个方法一个读锁的方法 一个写锁的方法
public interface ReadWriteLock {
    Lock readLock();
    Lock writeLock();
}

ReentrantReadWriteLock 的构造方法 

   //构造方法支持公平锁和非公平锁的创建。(默认为非公平锁。效率更高)初始化读写锁   
   // ReadLock 与 WriteLock 是其内部类
   public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }

    public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
    public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }

 ReadLock的初始化

    public static class ReadLock implements Lock, java.io.Serializable {
        private static final long serialVersionUID = -5992448646407690164L;
        private final Sync sync;
        
        //通过内部类SYN继承于AQS来实现读锁
        protected ReadLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }

     接下来看下syn非公平锁的实现
    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 6317671515068378041L;

        // 分享的划分,将32位划分为高16位和低16位 高16位是读锁 数量 低16位表示写锁数量
        static final int SHARED_SHIFT   = 16;
        static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
        static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
        static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

         //SHARED_UNIT    的值为
        // 0000 0000 0000 0001 0000 0000 0000 0000
        // MAX_COUNT      的值为 即65535 2的16次方-1
        //   0000 0000 0000 0000 1111 1111 1111 1111
      
        //sharedCount 
       // 假设c=1 c无符号右移 16位 高位补0无论正负
       //  0000 0000 0000 0000 0000 0000 0000 0001
       // sharedCount 返回的值为
       //  0000 0000 0000 0001 0000 0000 0000 0000

        static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
     //  exclusiveCount的值 真值表:1&0=0 1&1=1 0&0=0 0&1=0
     //  0000 0000 0000 0000 0000 0000 0000 0001 &
     //  0000 0000 0000 0000 1111 1111 1111 1111
     //   得到的值为  0000 0000 0000 0000 0000 0000 0000 0001 为1  
        static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

接下来看ReadLock是怎么上锁的

       public void lock() {
            sync.acquireShared(1);
        }

     public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
     }
    
       protected final int tryAcquireShared(int unused) {
 
            Thread current = Thread.currentThread();
            int c = getState();
         // 独占模式下线程数量 即 写锁的state值不为0
         // 如果写锁被占用 并且占用写锁的线程不是当前线程 那么返回-1
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            // r 是读锁的数量  r < MAX_COUNT 小于最大的数量 65536 
           // !readerShouldBlock() 读锁申请是否需要等待、资源是否被写锁占用 后面详细讲
          // CAS 操作成功  compareAndSetState(c, c + SHARED_UNIT))在高16位的基础上操作 操作读锁
            int r = sharedCount(c);
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {
                 // r==0 表示 读锁没有被占用
                 if (r == 0) {
                     // 标记第一个读锁的是当前线程
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {
                    // 读锁可重入
                     firstReaderHoldCount++;
                } else {
                    // 如果读锁的状态不为0 即有其他线程正在读取资源
                    HoldCounter rh = cachedHoldCounter;
                    //如果HoldCounter 一直为空或者不为 当前线程
                    if (rh == null || rh.tid != getThreadId(current))
                         // cachedHoldCounter 存入当前值
                        cachedHoldCounter = rh = readHolds.get();
                    else if (rh.count == 0)
                        // readHold设置当前值 一直是第一个值
                        readHolds.set(rh);
                       // count+1
                       rh.count++;
                }
                return 1;
            }
             // 当有写锁正在占用资源的时候、进入该方法
            return fullTryAcquireShared(current);
        }


    //上述的readerShouldBlock源码
    // 判断同步队列中 头部节点不为空 并且第二个节点不为空 并且头部接口以独占模式等待、返回true
    // 说明写锁已经获取到锁资源,并且还有其他线程正在申请写锁 、read锁资源直接失败
    final boolean apparentlyFirstQueuedIsExclusive() {
        Node h, s;
        return (h = head) != null &&
            (s = h.next)  != null &&
            !s.isShared()         &&
            s.thread != null;
    }

  // 当资源被写锁占用的时候调用fullTryAcquireShared
final int fullTryAcquireShared(Thread current) {

            HoldCounter rh = null;
            for (;;) {
                int c = getState();
                if (exclusiveCount(c) != 0) {
                    if (getExclusiveOwnerThread() != current)
                        return -1;
                    // else we hold the exclusive lock; blocking here
                    // would cause deadlock.
                } else if (readerShouldBlock()) {
                    // Make sure we're not acquiring read lock reentrantly
                    if (firstReader == current) {
                        // assert firstReaderHoldCount > 0;
                    } else {
                        if (rh == null) {
                            rh = cachedHoldCounter;
                            if (rh == null || rh.tid != getThreadId(current)) {
                                rh = readHolds.get();
                                if (rh.count == 0)
                                    readHolds.remove();
                            }
                        }
                        if (rh.count == 0)
                            return -1;
                    }
                }
                if (sharedCount(c) == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                if (compareAndSetState(c, c + SHARED_UNIT)) {
                    if (sharedCount(c) == 0) {
                        firstReader = current;
                        firstReaderHoldCount = 1;
                    } else if (firstReader == current) {
                        firstReaderHoldCount++;
                    } else {
                        if (rh == null)
                            rh = cachedHoldCounter;
                        if (rh == null || rh.tid != getThreadId(current))
                            rh = readHolds.get();
                        else if (rh.count == 0)
                            readHolds.set(rh);
                        rh.count++;
                        cachedHoldCounter = rh; // cache for release
                    }
                    return 1;
                }
            }
        }

如果上面tryAcquireShared(arg) < 0条件成立,那么尝试获取读锁的资源失败

那么执行doAcquireShared方法。将当前线程添加到同步队列中的尾部 自旋尝试去获取读锁资源

接下来我们来看下释放读锁的源码

        public void unlock() {
            sync.releaseShared(1);
        }

        public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
       }

      protected final boolean tryReleaseShared(int unused) {
            Thread current = Thread.currentThread();
            // 对HoldCounter 缓存的一些信息进行操作
            // 释放当前线程的信息
            if (firstReader == current) {
          
                if (firstReaderHoldCount == 1)
                    firstReader = null;
                else
                    firstReaderHoldCount--;
            } else {
                HoldCounter rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    rh = readHolds.get();
                int count = rh.count;
                if (count <= 1) {
                    readHolds.remove();
                    if (count <= 0)
                        throw unmatchedUnlockException();
                }
                --rh.count;
            }
            // 自旋去减少读锁的数量
            for (;;) {
                int c = getState();
                int nextc = c - SHARED_UNIT;
                if (compareAndSetState(c, nextc))
                    // 当所有的读锁都释放掉了以后返回true 执行doReleaseShared方法
                    return nextc == 0;
            }
        }


        private void doReleaseShared() {
 
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

接下来看writeLock的上锁源码 写锁就是独占模式的上锁。源码比较简单

        public void lock() {
            sync.acquire(1);
        }

           public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

 protected final boolean tryAcquire(int acquires) {
     
            Thread current = Thread.currentThread();
            int c = getState();
            int w = exclusiveCount(c);
            // C不为0 说明锁被占用,要么是读锁 要么是写锁
            if (c != 0) {
                // 如果写锁w状态值为0 或者占用写锁的不是当前线程 返回false 加入到等待队列中尝试获取锁
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                 
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // 设置写锁的state状态为1 写锁成功
                setState(c + acquires);
                return true;
            }
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ReentrantReadWriteLock原理分析 的相关文章

  • Java:获取当前正在执行的Method对应的对象

    将当前正在执行的方法作为 Method 对象获取的最优雅的方法是什么 我的第一个明显的方法是在辅助类中使用静态方法 该方法将加载当前线程堆栈 获取正确的堆栈跟踪元素 并根据其信息构造 Method 元素 有没有更优雅的方法来实现这一目标 这
  • 如何以编程方式找出我的 PermGen 空间使用情况?

    我正在尝试诊断java lang OutOfMemoryError PermGen Space在 Sun 的 Hotspot JVM 上运行时出现错误 并且想知道我的程序在不同时刻使用了多少 PermGen 空间 有没有办法以编程方式找出这
  • Java - 从配置文件加密/解密用户名和密码

    我们正忙于为客户开发 Java Web 服务 有两种可能的选择 将加密的用户名 密码存储在Web服务客户端上 从配置中读取 文件在客户端 解密并发送 将加密的用户名 密码存储在 Web 服务器上 从配置中读取 Web 服务器上的文件 解密并
  • H264 字节流到图像文件

    第一次来这里所以要温柔 我已经在给定的 H 264 字节流上工作了几个星期 一般注意事项 字节流不是来自文件 它是从外部源实时提供给我的 字节流使用 Android 的媒体编解码器进行编码 当将流写入扩展名为 H264的文件时 VLC能够正
  • JBoss AS 5 中的共享库应该放在哪里?

    我是 Jboss 新手 但我有多个 Web 应用程序 每个应用程序都使用 spring hibernate 和其他开源库和 portlet 所以基本上现在每个 war 文件都包含这些 jar 文件 如何将这些 jar 移动到一个公共位置 以
  • 以点作为分隔符分割字符串

    我想知道我是否要在一个字符串上分割字符串 正确的方式 我的代码是 String fn filename split return fn 0 我只需要字符串的第一部分 这就是我返回第一项的原因 我问这个是因为我在 API 中注意到 意味着任何
  • org.postgresql.util.PSQLException:协议错误。会话设置失败

    我知道这些类型的问题已经存在 但提供的解决方案对我不起作用 在我的应用程序中 没有版本不匹配的黑白驱动程序和 PostgreSQL 服务器 我还没有找到任何其他解决方案 我正在使用 PostgreSQL 服务器 9 4 和 postgres
  • 动画图像视图

    目前我正在开发一款游戏 这是我的游戏的详细信息 用户应选择正确的图像对象 我希望图像从左到右加速 当他们到达终点时 他们应该再次出现在活动中 这是我正在处理的屏幕截图 我有 5 个图像视图 它们应该会加速 您有此类动画的示例代码吗 非常感谢
  • Codility 钉板

    尝试了解 Codility NailingPlanks 的解决方案 问题链接 https app codility com programmers lessons 14 binary search algorithm nailing pla
  • 更改 JTextPane 的大小

    我是Java新手 刚刚在StackOverflow中找到了这段代码 ResizeTextArea https stackoverflow com questions 9370561 enabling scroll bars when jte
  • Hystrix是否可以订阅CircuitBreaker开启事件?

    对于单元测试 我希望能够订阅 Hystrix 事件 特别是在断路器打开或关闭时发生事件 我四处寻找示例 似乎解决方法是利用指标流并监视断路器标志 由于 Hystrix 是基于 RxJava 构建的 我认为应该在某个地方有一个事件订阅接口 在
  • 如何在Gradle中支持多种语言(Java和Scala)的多个项目?

    我正在尝试将过时的 Ant 构建转换为 Gradle 该项目包含约50个Java子项目和10个Scala子项目 Java 项目仅包含 Java Scala 项目仅包含 Scala 每个项目都是由 Java 和 Scala 构建的 这大大减慢
  • 删除 ArrayList 对象问题

    我在处理作业时遇到从 ArrayList 中删除对象的问题 如果我使用 正常 for 循环 它的工作原理如下 public void returnBook String isbn for int i 0 i lt booksBorrowed
  • C++ 中的 Java ArrayList [重复]

    这个问题在这里已经有答案了 在Java中我可以做 List
  • 如何在 Java 中创建要打印到 JFrame 的 JLabels 数组

    我正在尝试制作一系列标签 每个标签都有一个来自函数的不同值 我不知道要使用的标签的确切数量 我的意思是可以打印任意数量的值 请帮我做这件事 很简单 只需一个方法返回一个数组或一些 JLabels 集合 并将它们全部添加到您的 JCompon
  • 无法仅在控制台中启动 androidstudio

    你好 我的问题是下一个 我下载了Android Studio如果我去 路径 android studio bin 我执行studio sh 我收到以下错误 No JDK found Please validate either STUDIO
  • 如何将任务添加到 gradle 中的主要“构建”任务

    当我尝试使用以下代码将任务添加到主构建任务时 rootProject tasks getByName build dependsOn mytask 当我跑步时它抱怨gradle w build输出 Where Build file line
  • java中wav文件转换为字节数组

    我的项目是 阿塞拜疆语音的语音识别 我必须编写一个程序来转换wav文件到字节数组 如何将音频文件转换为byte 基本上如第一个答案中的片段所描述 但不是BufferedInputStream use AudioSystem getAudio
  • 条件查询:按计数排序

    我正在尝试执行一个标准查询 该查询返回 stackoverflow 中回答最多的问题 例如常见问题解答 一个问题包含多个答案 我正在尝试使用标准查询返回按每个问题的答案数排序的回答最多的问题 任何人都知道我应该在 hibernate cri
  • 在多线程环境中,Collections.sort 方法有时会抛出 ConcurrentModificationException。列表没有进行结构性修改

    package CollectionsTS import java util ArrayList import java util Collections import java util HashSet import java util

随机推荐

  • c语言 char / short / int中能存储多大的数据?

    我们都知道char能存储的数据范围是 128 127 unsigned chard 范围是0 255 short能存储的数据范围是 32768 32767 unsigned short 范围是0 65535 int能存储的数据范围是 214
  • 【C#:学生信息管理系统-部分代码1】

    创建项目Windows窗体应用程序 创建一个DBhelp cs类 连接数据库 每次只用调用这个类就行了 这样调用 DBhelp conn Open using System using System Collections Generic
  • Open3D 计算点云包围盒

    目录 一 主要函数 1 AABB包围盒 1 1获取包围盒边长 2 OBB包围盒 2 1获取包围盒边长 二 代码实现 三 结果展示 一 主要函数 1 AABB包围盒 1 get axis aligned bounding box 获取aabb
  • 【AD20学习笔记】原理图库基础概念篇

    2022 7 6 暑假里还是跟着凡亿教育的课程再学习一遍吧 虽然这个课程已经看了很多遍了 但老是记不住 实战的时候需要在调视频看 很难受 这次再系统完整地跟一遍基础和实战 在这里记录一下 我这里一般是看完一堂课就开画了 不是很系统详细写 仅
  • clickhouse数据导入遇到的问题

    1 采用mybatis写入数据 速度很慢的问题 采用mybatis拼接sql的方式 可以写入数据 但是效率很低 每秒数据大概200 300条数据记录 2 采用jdbc写入数据 可以使用两种数据源 新版本的包 import com click
  • 如何画出一张合格的技术架构图?

    阿里妹导读 技术传播的价值 不仅仅体现在通过商业化产品和开源项目来缩短我们构建应用的路径 加速业务的上线速率 也体现在优秀工程师在工作效率提升 产品性能优化和用户体验改善等经验方面的分享 以提高我们的专业能力 接下来 阿里巴巴技术专家三画
  • 使用Vue过滤器格式化日期时间

    学习目标 掌握Vue过滤器 created mounted beforeDestory 等函数 学习内容 Vue过滤器 created mounted beforeDestory 实现思路 1 使用Vs code创建demo html文件
  • php下载xlsx到本地,Laravel 5.8 实现Excel 下载(将信息数据导出成Excel下载到本地)...

    目的 实现将数据表信息导出成Excel下载到本地 注 我这里用Laravel 版本 5 8 框架中实现并讲解 在laravel使用第3方插件库来完成此项工作 下面我为大家提供3个链接 供大家参考 1 Excel插件库 2 Excel官网 3
  • Mina框架及接口开发

    Mina是NIO实现的一个架构 可以通过它快速开发网络通信 中间件等服务端程序 IOService接口 描述服务端和客户端接口 子类是connector和Acceptor分别用于描述客户端和服务端 IOproceser 多线程环境来处理我们
  • 【Vue2从入门到精通】Vue监听器watch,计算属性computed,方法methods的区别及使用场景

    文章目录 人工智能福利文章 前言 Vue 监听器 watch 定义及作用 示例 使用场景 Vue 计算属性 computed 定义及作用 示例 使用场景 Vue 方法 methods 定义及作用 示例 使用场景 总结 脑筋急转弯小程序抢先体
  • echarts 根据实际数据改变仪表盘颜色

    情景 实际值 gt 计划值 仪表盘颜色显示 绿色表示超过预期 相反则表示未达到预期 如图 js 控制 方法 if factVal gt planVal curOption series 1 axisLine lineStyle color
  • React中实现tab切换

    代码比较简单 并不复杂 import React Component from react class Taball extends Component constructor props super props this state cu
  • 【ML特征工程】第 3 章 :文本数据:扁平化、过滤和分块

    大家好 我是Sonhhxg 柒 希望你看完之后 能对你有所帮助 不足请指正 共同学习交流 个人主页 Sonhhxg 柒的博客 CSDN博客 欢迎各位 点赞 收藏 留言 系列专栏 机器学习 ML 自然语言处理 NLP 深度学习 DL fore
  • MATLAB算法实战应用案例精讲-【数模应用】KMP字符串匹配(附C语言和Java代码)

    目录 前言 几个高频面试题目 KMP 算法和暴力匹配算法之间的区别 和动态规划有什么关系
  • eolinker搭建(Linux版)

    1 什么是eolinker Eolinker是一个api管理平台 可满足各行业客户在不同应用环境中对接口管理全生命周期的个性化需求 提供API开发管理 开发团队协作 自动化测试 网关以及监控等服务 帮助企业实现开发运维一体化 提升开发速度并
  • 极速版RPS选股,一秒出结果的方案是如何实现的!股票量化分析工具QTYX-V2.5.3...

    概述RPS选股策略 在国内大家可能对彼得 林奇 Peter Lynch 沃伦 巴菲特 Warren E Buffett 这些华尔街 wall street 的金融大鳄耳熟能详 其实威廉 欧奈尔 William J O Neil 的投资成就同
  • 自行装机配置

    2022年CPU天梯图 更新13代酷睿 锐龙7000 知乎 2022年显卡天梯图 更新4090 A770 A750 知乎 显卡 华硕显卡分级 华硕显卡系列 定位 价格 DUAL雪豹 丐版 便宜实惠 ATS巨齿鲨 中端 比DUAL系列贵 TU
  • 51 Proteus仿真频率计速度计超速报警数码管显示MAX7219-0001

    Proteus仿真小实验 51 Proteus仿真频率计速度计超速报警数码管显示MAX7219 0001 功能 硬件组成 51单片机 8位数码管 MAX7219数码管驱动模块 多个按键 LED灯 蜂鸣器 1 准确测量信号发生器输出的方波频率
  • Qt对象树

    01 什么是对象树 是用来组织和管理所有 QObject及其子类创建的对象 父对象 this 或 setParent 02 对象树的基本规则 对象树创建规则 对于Qt程序来说 父对象通常创建在栈上 子对象应创建在堆中 new 无需手动 de
  • ReentrantReadWriteLock原理分析

    在介绍ReentrantReadWriteLock读写锁原理之前 先来说下写锁与读锁 方便后续大家的理解 1 当资源被写锁占用时 此时是不允许去读的 只有当写锁被释后读锁才能去申请资源 2 当资源没有被写锁占用时 多个线程是可以共享资源 写