ReentrantReadWriteLock源码分析

ReentrantReadWriteLock是基于AQS实现的读写锁,读锁与读锁不互斥、读锁与写锁互斥、写锁与写锁互斥。

类的继承关系

AQS提供了共享和排它两种模式,acquire/release、acquireShared/releaseShared 是AQS里面的两对模板方法。写锁是排它模式基于acquire/release模板方法实现的,读锁是共享模式基于acquireShared/releaseShared这对模板方法实现的。

public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable {
    //读锁
    private final ReentrantReadWriteLock.ReadLock readerLock;
    //写锁
    private final ReentrantReadWriteLock.WriteLock writerLock;
    //AQS的实现
    final Sync sync;

    public ReentrantReadWriteLock() {
        this(false);
    }

    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new ReentrantReadWriteLock.FairSync() : new         ReentrantReadWriteLock.NonfairSync();
        readerLock = new ReentrantReadWriteLock.ReadLock(this);
        writerLock = new ReentrantReadWriteLock.WriteLock(this);
    }

    public static class ReadLock implements Lock, java.io.Serializable {
        private final Sync sync;
        protected ReadLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }
    }

    public static class WriteLock implements Lock, java.io.Serializable {
        private final Sync sync;
        protected WriteLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }
    }
}

从表面上看ReentrantReadWriteLock内部定义了readerLock和writerLock两把锁,实际上他们公用一个sync对象,可以理解为一把锁。线程分为两类:读线程和写线程。读线程和读线程之间不互斥,可以同时拿到这把锁。读线程和写线程互斥,写线程和写线程互斥。

从构造函数中可以看出,sync对象和互斥锁ReentrantLock一样有两种实现模式:公平和非公平模式。

abstract static class Sync extends AbstractQueuedSynchronizer {
    static final int SHARED_SHIFT   = 16;
    //左移运算符(<<)将一个数的二进制表示向左移动指定的位数,右侧空出的位置补0。
    //SHARED_UNIT的二进制为1 0000 0000 0000 0000  十进制65536
    static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
    //MAX_COUNT二进制为1111 1111 1111 1111 十进制65535
    static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
    //EXCLUSIVE_MASK二进制为1111 1111 1111 1111 十进制65535
    static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
    //无符号右移运算符(>>>)将一个数的二进制表示向右移动指定的位数,
    //但左侧空出的位置都填充0,不考虑正负号。
    //例如7>>>2 3的二进制是111,向左移动两位得到001 十进制1
    //持有读锁的线程重入次数
    static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
    //& 按位与运算,两位同时为“1”得1,否则为0,
    //EXCLUSIVE_MASK的32位表示为0000 0000 0000 0000 1111 1111 1111 1111,
    //所以实际上只有低位的16为会参与计算。
    //持有写锁的线程重入次数
    static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

}

读写锁也是用state变量来表示锁状态的,只不过是把state变量拆成两半(int占4字节32位),低16位,用来记录写锁的重入次数,高16位,用来记录“读”锁的重入次数。之所以把int拆分成两半而不是用两个int型变量分别表示读锁和写锁的状态是因为无法用一次CAS 同时操作两个int变量。

当state=0时,说明既没有线程持有读锁,也没有线程持有写锁;当state!=0时,要么有线程持有读锁,要么有线程持有写锁。需要进一步通过sharedCount(state)和exclusiveCount(state)判断到底是读线程还是写线程持有了该锁。

WriteLock  acquire 
    public final void acquire(int arg) {
        //tryAcquire 尝试去获取锁,如果获取失败,则加入到等待队列
        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);
            if (c != 0) {
                //state不等于0,有线程持有锁,需要进一步判断是读线程还是写线程持有锁
                //w=0写锁为0,说明当前一定是读线程拿到锁,读写互斥,写线不能去拿锁。
                //w!=0且持有写锁的线程不是当前线程,写写互斥,当前写线程不能拿到锁。
                // (Note: if c != 0 and w == 0 then shared count != 0)
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                //如果当前写线程写锁重入的次数大于最大限制,抛出异常
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
               //重入
                setState(c + acquires);
                return true;
            }
            //state=0,此时既没有读线程也没有写线程持有锁,可以去抢锁
            //writerShouldBlock 判断写线程在抢锁的时候是否需要排队,有公平锁和非公平锁两种实现
            //非公平锁实现,写线程可以直接抢锁,公平锁实现,如果等待队列中有其它线程在排队,则不能去抢锁。
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }
WriteLock release
    public final boolean release(int arg) {
        //尝试释放锁,若释放锁成功,尝试唤醒等待队列头部的线程
        if (tryRelease(arg)) {
            //释放锁成功,唤醒等待队列中头节点的下一个节点代表的线程
            Node h = head;
            //h != null && h.waitStatus != 0
            //说明头节点的后继节点右线程在等待被唤醒
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

        protected final boolean tryRelease(int releases) {
            //如果持有写锁的不是当前线程,则抛出异常
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            //减少重入次数,如果重入次数为0,直接释放锁
            int nextc = getState() - releases;
            boolean free = exclusiveCount(nextc) == 0;
            if (free)
                setExclusiveOwnerThread(null);
            setState(nextc);
            return free;
        }
ReadLock  acquireShared
    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();
            //写锁被某个线程持有,并且这个线程不是当前线程,无法获取读锁
            //这个可以看出一个线程在持有写锁时,可以再去拿读锁
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            //读锁的数量
            int r = sharedCount(c);
            //readerShouldBlock方法判断写线程去获取锁时,是否需要排队。对应公平锁和非公平锁两种实现方式。
            //非公平锁实现:读线程在获取锁时,如果队列中第一个元素是写线程,则需要排队阻塞,这样做的目的是防止写线程一直拿不到锁
            //公平锁实现:读线程在获取锁时,如果队列中有其他线程在排队,就需要排队阻塞
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {
                if (r == 0) {
                    //firstReader 表示第一个读线程
                    firstReader = current;
                    //firstReaderHoldCount 第一个读线程重入的次数
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {
                   
                    firstReaderHoldCount++;
                } else {
                    //如果当前线程不是第一个获取到读锁的线程
                    //利用ThreadLocal记录该线程重入读锁的次数
                    HoldCounter rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current))
                        cachedHoldCounter = rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;
                }
                return 1;
            }
            //如果cas失败,循环获取锁,直到成功
            return fullTryAcquireShared(current);
        }
ReadLock  releaseShared
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

        protected final boolean tryReleaseShared(int unused) {
            Thread current = Thread.currentThread();
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
                if (firstReaderHoldCount == 1)
                    firstReader = null;
                else
                    firstReaderHoldCount--;
            } else {
                //从ThreadLocal中获取计数器
                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;
            }
            //这里使用了自旋锁加cas操作修改state的值
            //因为读锁是共享锁,存在多个线程竞争的情况
            for (;;) {
                int c = getState();
                int nextc = c - SHARED_UNIT;
                if (compareAndSetState(c, nextc))
                    // Releasing the read lock has no effect on readers,
                    // but it may allow waiting writers to proceed if
                    // both read and write locks are now free.
                    return nextc == 0;
            }
        }
总结

1、读写锁也是用int 类型的state变量来表示锁状态的,只不过是把state变量拆成两半(int占4字节32位),低16位,用来记录写锁的重入次数,高16位,用来记录“读”锁的重入次数。

2、读锁和读锁不互斥,读锁和写锁互斥,写锁和写锁互斥。

3、读锁的非公平锁实现:读线程在获取锁时,如果队列中第一个元素是写线程,则需要排队阻塞,这样做的目的是防止写线程一直拿不到锁。读锁的公平锁实现:读线程在获取锁时,如果队列中有其他线程在排队,就需要排队阻塞。

4、写锁非公平锁实现:写线程可以直接抢锁。公平锁实现:如果等待队列中有其它线程在排队,则不能去抢锁。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/605922.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Yii2 自动生成php代码

文档地址&#xff1a;入门&#xff08;Getting Started&#xff09;: 用 Gii 生成代码&#xff08;Generating Code with Gii&#xff09; - Yii 2.0 权威指南 - 文档 - Yii Framework 中文网 找到配置文件&#xff0c;以我的项目为例&#xff1a; 因为的是开启了路由美化所以访…

在线扭蛋机小程序:商家稳占市场的新突破口

近几年&#xff0c;扭蛋机进入了爆发期&#xff0c;动漫、游戏的发展更是推动了市场的发展&#xff0c;我国扭蛋机正在蓬勃发展中。 不过&#xff0c;在市场规模扩大下&#xff0c;扭蛋机行业的竞争力也在同时加大&#xff0c;企业商家需要在市场竞争中寻求发展新思路&#xf…

开源推荐榜【FunClip是一款完全开源、本地部署的自动化视频剪辑工具】

FunClip是一款完全开源、本地部署的自动化视频剪辑工具&#xff0c;通过调用阿里巴巴通义实验室开源的FunASR Paraformer系列模型进行视频的语音识别&#xff0c;随后用户可以自由选择识别结果中的文本片段或说话人&#xff0c;点击裁剪按钮即可获取对应片段的视频&#xff08;…

基于EBAZ4205矿板的图像处理:12图像二值化(阈值可调)

基于EBAZ4205矿板的图像处理&#xff1a;12图像二值化(阈值可调) 我的项目是基于EBAZ4205矿板的阈值可调的图像阈值二值化处理&#xff0c;可以通过按键调整二值化的阈值&#xff0c;key1为阈值加1&#xff0c;key4为阈值减1&#xff0c;key2为阈值加10&#xff0c;key5为阈值…

【AI+音视频总结】如何在几分钟内用智能工具摘取音视频精华?揭秘下一代学习和内容创作神器!

今天无意发现一个网站&#xff0c;可以一步到位完成AI音视频总结。 我之前对于音视频总结的步骤还是借助 工具下载 剪映来完成的。详情可以参考之前写的一篇文章 【AI应用】模仿爆款视频二次创作短视频操作步骤 。 这里介绍的网站是 BibiGPT 。 BibiGPT AI 音视频助理 - 它是…

构建第一个ArkTS应用之@AppStorage:应用全局的UI状态存储

AppStorage是应用全局的UI状态存储&#xff0c;是和应用的进程绑定的&#xff0c;由UI框架在应用程序启动时创建&#xff0c;为应用程序UI状态属性提供中央存储。 和AppStorage不同的是&#xff0c;LocalStorage是页面级的&#xff0c;通常应用于页面内的数据共享。而AppStora…

VMare Workstation安装ubuntu虚拟机异常问题处理

安装方法 ubuntu官网下载插件 异常处理 开启时报错"unable to proceed without a log file" 遇到此问题的都有一个共同点&#xff0c;工作目录路径上都带了数字&#xff0c;比如"Ubuntu 64位 01"&#xff0c;解决方法为&#xff1a; 选中"Ubuntu 64位…

可视化大屏的应用:电子政务领域的巨大应用价值

可视化大屏在电子政务领域的应用价值主要体现在以下几个方面&#xff1a; 数据监控与分析 可视化大屏可以将政务数据以图表、地图等形式展示在大屏上&#xff0c;帮助政府部门实时监控和分析各项指标和数据变化。例如&#xff0c;可以实时显示人口统计、经济指标、环境监测等…

利用“AnaTraf“网络流量分析仪轻松诊断和优化网络

网络性能监测和诊断(NPMD)是网络管理和优化的重要环节,准确快速地定位和排除网络故障对于保障业务正常运转至关重要。作为一款专业的网络流量分析设备,AnaTraf网络流量分析仪凭借其强大的流量分析和故障诊断功能,为网络管理者提供了一个高效的网络优化解决方案。 全面掌握网络…

如何将pdf文件换成3d模型?---模大狮模型网

PDF文件是一种广泛用于文档传输和共享的格式&#xff0c;但在某些情况下&#xff0c;我们可能希望将其中的内容转换为更具交互性和视觉效果的3D模型。本文将介绍如何将PDF文件转换为3D模型&#xff0c;为您展示实现这一想象的步骤。 选择合适的PDF文件&#xff1a; 首先&#…

利用AnaTraf网络流量分析仪轻松解决网络故障问题

网络故障是每个企业都必须面对的头痛问题。如何快速定位并解决网络问题,不仅能提高员工工作效率,也能减少因网络问题而带来的经济损失。 AnaTraf网络流量分析仪就是为解决这一问题而问世的一款高性能网络诊断工具。它能够对网络流量进行全面的实时监控和分析,帮助网络管理员快…

软考中级之数据库系统工程师笔记总结(六)多媒体基础

作者&#xff1a;Maynor 博客之星大数据领域Top1,GitHub项目awesome-chatgpt-project作者, 大厂程序员, 全网技术矩阵粉丝7w 公众号&#xff1a;Maynor996&#x1f4e2;博客主页&#xff1a;https://manor.blog.csdn.net &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &am…

请求响应里面的日期参数

日期参数 需要在控制类使用DateTimeFormat注解 package com.ming.controller; ​ ​ import org.springframework.format.annotation.DateTimeFormat; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.Rest…

1756jsp农产品销售管理系统Myeclipse开发mysql数据库C2C模式java编程计算机网页项目沙箱支付

一、源码特点 java 农产品销售管理系统 是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统采用web模式&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开发&#xff0…

在做题中学习(53): 寻找旋转数组中的最小值

153. 寻找旋转排序数组中的最小值 - 力扣&#xff08;LeetCode&#xff09; 解法&#xff1a;O(logn)->很可能就是二分查找 思路&#xff1a;再看看题目要求&#xff0c;可以画出旋转之后数组中元素的大小关系&#xff1a; 首先&#xff0c;数组是具有二段性的(适配二分查…

8.1 AWS创建用户池(Amazon Cognito)和用户

AWS创建用户池&#xff08;Amazon Cognito&#xff09;和用户 目录一、Amazon Cognito1. 创建用户池2. 添加用户 目录 一、Amazon Cognito Amazon Cognito: https://aws.amazon.com/cognito/ Amazon Cognito 是亚马逊提供的一种身份验证、授权和用户管理服务。它为开发人员提供…

韩顺平0基础学Java——第6天

p87-p109 运算符&#xff08;第四章&#xff09; 四种进制 二进制用0b或0B开头 十进制略 八进制用0开头 十六进制0x或0X开头&#xff0c;其中的A—F不区分大小写 10转2&#xff1a;将这个数不断除以2&#xff0c;直到商为0&#xff0c;然后把每步得到的余数倒过来&#…

数据结构--链表进阶面试题

在链表题目开始之前我们来复习一道数组元素的逆序问题&#xff1a; 给定一个整数数组 nums&#xff0c;将数组中的元素向右轮转 k 个位置&#xff0c;其中 k 是非负数。 提示&#xff1a; 1 < nums.length < 10^5-2^31 < nums[i] < 2^31 - 10 < k < 10^5 思…

AlphaFold3(AF3)简单介绍:预测各种生物分子结构和它们之间相互作用的深度学习模型

参考: 文章地址: https://www.nature.com/articles/s41586-024-07487-w https://blog.google/technology/ai/google-deepmind-isomorphic-alphafold-3-ai-model/ AlphaFold3体验官网: https://golgi.sandbox.google.com/ 《Accurate structure prediction of biomolecula…

分享一个php常驻内存多进程任务的扩展

前言 最近在摸鱼的时候发现一个PHP常驻内存多进程任务扩展包&#xff1a;EasyTask: PHP常驻内存多进程任务管理器&#xff0c;支持定时任务(PHP resident memory multi-process task manager, supports timing tasks) (gitee.com)&#xff0c;支持php使用多线程处理任务。之前…
最新文章