从ReentrantReadWriteLock开始的独占锁与共享锁的源码分析

news2025/7/22 5:30:51

FBI WARNING(bushi)

当涉及sync调用时,并不会分析尝试获取和释放之后的后继逻辑,因为这个逻辑是由AQS类实现的。请看姊妹篇之并发入门组件AQS源码解析。

开始的开始是一个demo

以下的代码,会将独占锁持有5分钟,在此期会阻塞住后面两个线程的共享锁请求,独占锁释放后,两个线程会同时拥有共享锁,即使没有进行unlock()

public class lockTest {

    public static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    static ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
    static ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();

    public static void main(String[] args) {
        new Thread(()->{
            writeLock.lock();
            try {
                Thread.sleep(300000);
                System.out.println("写锁释放");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            writeLock.unlock();
        }).start();
        new Thread(()->{
            readLock.lock();
            try {
                System.out.println("读锁1");
                Thread.sleep(300000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            readLock.unlock();
        }).start();
        new Thread(()->{
            readLock.lock();
            try {
                System.out.println("读锁1");
                Thread.sleep(300000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            readLock.unlock();
        }).start();
    }
}

ReentranReadWriteLock,writeLock,readLock,AQS的关系

让我们先进入第一行,
public static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
可以看到,默认创建了一个非公平的AQS,然后将这个AQS分别给了读锁和写锁。
请添加图片描述
请添加图片描述
而后面两行

static ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
static ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
就只是返回已经建立好的对象
请添加图片描述

独占锁获取

进入

我们进入上面的代码writeLock.lock();
在这里插入图片描述
进入了老熟客acquire方法。
在这里插入图片描述

核心方法

总体大概思路是,判断是否锁是否有被使用,如果有被使用而是自己,那重入,不然就失败,如果没被使用就设定为自己。

protected final boolean tryAcquire(int acquires) {
            Thread current = Thread.currentThread();
            //获取状态并且检查独占进入次数
            int c = getState();
            int w = exclusiveCount(c);
            if (c != 0) {
                // 独占次数为0,但是状态却不为0,那么说明现在存在着共享锁。如果独占锁不为自己,状态却不为0,那么说明现在存在着独占锁,这两种情况都无法获取到锁
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                // 达到MAX_COUNT说明重入了65536次,九成九九九都是代码写错了!
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // 状态加上当前重入次数
                setState(c + acquires);
                return true;
            }
            //前一个条件是公平锁和非公平锁的关键差异。后一个,如果c发生了变化,说明有线程同样参与了该AQS状态的变化并且早于自己完成了修改,那么默认自己失败,放弃。
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            //将独占锁所有者设定为自己,并返回成功获取
            setExclusiveOwnerThread(current);
            return true;
        }

请添加图片描述

state

state是AQS里面一个32位长的整型。
private volatile int state;
后16位说明独占锁状态,数值表示这个独占锁被持有者重入了多少次。
前16位说明共享锁状态,数值表示这个共享锁被持有了多少次。(请注意,一个线程可以多次持有读锁)

公平与非公平的界定

我们可以清晰看到,公平和非公平只在于writerShouldBlock()和readerShouldBlock()的重写
请添加图片描述
先看看非公平锁,非公平锁并不会对独占进行操作
请添加图片描述
再看看公平锁,写锁会进行判断。如果队列不为空,并且队列里争抢头节点的线程不为自己,说明有线程在排队,于是tryAcquired失败,
请添加图片描述

请添加图片描述

独占锁释放

请添加图片描述
实际上release返回false并不重要,我们可以看出上层并没有对其返回值进行逻辑操作。顺带一提,unparkSuccessor也在AQS篇。
请添加图片描述

protected final boolean tryRelease(int releases) {
	// 进行锁持有检验与释放
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
     int nextc = getState() - releases;
     // 如果释放了之后不为0,说明重入了,那么在release层就直接返回false而不执行后续的唤醒后继者。
    boolean free = exclusiveCount(nextc) == 0;
    if (free)
        setExclusiveOwnerThread(null);
    setState(nextc);
    return free;
}

共享锁获取

进入

回到最初的最初,让我们看一下共享锁的获取流程
请添加图片描述
调用的是ReadLock下面的lock(),可以看出,也是对sync进行调用操作,只不过独占锁调用的是acquire,而共享锁调用的是acquireShared
请添加图片描述
try尝试获取一下
请添加图片描述

核心方法

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);
    //如果成功获取,那么必定返回1
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {
        if (r == 0) {
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
            firstReaderHoldCount++;
        } else {
        	//如果缓存不是自己的HoldCounter,就去ThreadLocalMap取出线程私有的HoldCounter,存入缓存
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            // 如果自己缓存数量为0,就放入count为1的HoldCounter
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
        }
        return 1;
    }
    //如果不是因为存在独占锁,而是因为共享锁共同compareAndSetState导致失败,或者队列中首线程正在争抢独占锁(还没抢到),那么就会进入完全尝试
    return fullTryAcquireShared(current);
}

请添加图片描述
请添加图片描述
如果头节点不为空,并且头节点的下一个不为空,并且头节点的下一个不是共享的,并且该节点没有被取消,那么就判定正在争抢锁的线程是抢独占锁的。如果对方抢独占锁,那就直接放行,这是为了避免前面判断完了非独占,但是到了这一行,其他线程又已经上了独占锁这种尴尬现象。
请添加图片描述

进入完全尝试

完全尝试分为三部分,一,有独占锁且不是自己,直接失败。二,自己没有获取过读锁只是首次尝试,直接返回false,不再尝试。三,前两者都通过的情况下,尝试成功获取锁。

        final int fullTryAcquireShared(Thread current) {
            HoldCounter rh = null;
            for (;;) {
                int c = getState();
                //如果存在独占锁,但不是自己,直接失败,
                if (exclusiveCount(c) != 0) {
                    if (getExclusiveOwnerThread() != current)
                        return -1;
				//readerShouldBlock上面有解析,说明有线程在抢独占锁
                } else if (readerShouldBlock()) {
                    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;
                }
            }
        }

释放共享锁

进入

执行readLock.unlock();
在这里插入图片描述
在这里插入图片描述

核心方法

protected final boolean tryReleaseShared(int unused) {
            Thread current = Thread.currentThread();
            // 如果是第一个获取共享锁的,直接将firstReader改为null
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
                if (firstReaderHoldCount == 1)
                    firstReader = null;
                else
                    firstReaderHoldCount--;
            } else {
            	// 否则取出当前线程的重入次数,并减一,如果只剩一次了,那么就从map中删除当前线程的计数器
                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;
            }
            //一直尝试将状态值减1直到成功
            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;
            }
        }

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【LSTM实战】股票走势预测全流程实战(stock predict)

任务&#xff1a;利用LSTM模型预测2017年的股票中High的走势&#xff0c;并与真实的数据进行比对。数据&#xff1a;https://www.kaggle.com/datasets/princeteng/stock-predict 一、import packages|导入第三方库 import pandas as pd import matplotlib.pyplot as plt impo…

利用ESP32实现蓝牙通信的方法

​大家好&#xff0c;我是ST! 上次给大家分享了如何使用ESP32实现UDP通信&#xff0c;今天跟大家聊聊如何使用ESP32实现蓝牙通信。 目录 一、蓝牙简介 二、miropython有关蓝牙的实现方法 三、我的实验代码 四、手机调试APP 一、蓝牙简介 蓝牙是一种无线通讯技术&#xff…

Linux篇【5】:Linux 进程概念(三)

目录 四、进程状态 4.1、各个操作系统下的进程状态&#xff1a; 4.1.1、进程的运行态&#xff1a; 4.1.2、进程的终止态(退出态)&#xff1a; 4.1.3、进程的阻塞态&#xff1a; 4.1.4、进程的挂起态&#xff1a; 4.2、Linux 操作系统下的进程状态&#xff1a; 四、进…

30、Java高级特性——Java API、枚举、包装类、装箱和拆箱

目录 课前先导&#xff1a; 一、Java API 1、API 2、Java API 3、Java API常用包 二、枚举类型 1、枚举 2、枚举类 3、代码演示 3.1 创建枚举类 3.2 创建测试类 4、MyEclipse创建枚举类的快捷方式 三、包装类 1、八大基本数据类型包装类 2、包装类中的构造方…

Java并发编程之可见性分析 volatile

可见性 对于什么是可见性&#xff0c;比较官方的解释就是&#xff1a;一个线程对共享变量的修改&#xff0c;另一个线程能够立刻看到。 说的直白些&#xff0c;就是两个线程共享一个变量&#xff0c;无论哪一个线程修改了这个变量&#xff0c;则另外的一个线程都能够看到上一…

电脑可以通过蓝牙发送文件吗?电脑蓝牙怎么发送文件

蓝牙&#xff08;bluetooth&#xff09;是一种支持设备短距离通信的无线电技术。能在包括移动电话、PDA、无线耳机、笔记本电脑、相关外设等众多设备之间进行无线信息交换。蓝牙技术让数据传输变得更加迅速高效&#xff0c;为无线通信拓宽道路。随着蓝牙技术的发展&#xff0c;…

甘露糖-聚乙二醇-羧酸|mannose-PEG-COOH|羧酸-PEG-甘露糖

甘露糖-聚乙二醇-羧酸|mannose-PEG-COOH|羧酸-PEG-甘露糖 首先合成了二,三分支的甘露糖簇分子.甘露糖经烯丙 苷化,乙酰基保护后,将其烯丙基的双键氧化得到带有羧基连接臂的甘露糖衍生物,然后再分别与1,6-己二胺和三(2-氨乙基)胺进行缩合反应,后脱掉保 护基,得到二分枝甘露糖簇…

Azide-PEG-Thiol,N3-PEG-SH,叠氮-聚乙二醇-巯基可用来制备金纳米颗粒

1、名称 英文&#xff1a;Azide-PEG-Thiol&#xff0c;N3-PEG-SH 中文&#xff1a;叠氮-聚乙二醇-巯基 2、CAS编号&#xff1a;N/A 3、所属分类&#xff1a;Azide PEG Thiol PEG 4、分子量&#xff1a;可定制&#xff0c;5k N3-PEG-SH、20k 叠氮-聚乙二醇-巯基、10k N3-PE…

嵌入式分享合集105

一、智能灯光控制系统&#xff08;基于stm32&#xff09; 带你走进物联网的世界说一个整天方案哦 这次是基于stm32的 当然你可以用esp “智能光照灯”使用STM32作为系统的MCU&#xff0c;由于单片机IO口驱动电流过小&#xff0c;搭配三极管放大电流&#xff0c;从而满足光照强…

全网监控 nginx 部署 zabbix6.0

Zabbix监控 文章目录Zabbix监控一、zabbix6.0部署1、部署zabbix 6.0版本&#xff08;nginxphpzabbix&#xff09;1、nginx配置2、php配置3、mariadb配置二、zabbix配置1、zabbix配置 &#xff08;6.0&#xff09;1、源码安装2、zabbix rpm2、zabbix(5.0安装) -- 补充3、故障汇总…

【Linux】翻山越岭——进程地址空间

文章目录一、是什么写时拷贝二、为什么三、怎么做区域划分和调整一、是什么 回顾我们学习C/C时的地址空间&#xff1a; 有了这个基本框架&#xff0c;我们对于语言的学习更加易于理解&#xff0c;但是地址空间究竟是什么❓我们对其并不了解&#xff0c;是不是内存呢&#xff1…

【创建微服务】创建微服务并使用人人开源代码生成器生成基本代码

创建项目微服务 —— 添加模块 添加依赖 使用 人人开源代码生成器 快速生成 crud 代码 —— https://gitee.com/renrenio 下载导入人人开源项目后&#xff0c;修改 application.yml 文件下的数据库连接配置&#xff1a; 2. 修改 generator.properties 配置文件下的 主路径、包…

CC1101RGPR射频收发器 Low-Power Sub-1GHz 射频收发器

CC1101RGPR射频收发器 Low-Power Sub-1GHz 射频收发器 CC1101RGPR是一种低成本的 sub-1 GHz 收发器专为超低功耗无线应用而设计。该电路主要用于ISM&#xff08;工业、科学和医疗&#xff09;和SRD&#xff08;短程设备&#xff09;频段315、433、868 和 915 MHz&#xff0c;但…

【891. 子序列宽度之和】

来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 一个序列的 宽度 定义为该序列中最大元素和最小元素的差值。 给你一个整数数组 nums &#xff0c;返回 nums 的所有非空 子序列 的 宽度之和 。由于答案可能非常大&#xff0c;请返回对 109 7 取余 …

UNIAPP实战项目笔记40 设置和地址的页面布局

UNIAPP实战项目笔记40 设置和地址的页面布局 my-config.vue 设置页面布局 具体图片自己替换哈&#xff0c;随便找了个图片的做示例 代码 my-config.vue 页面部分 <template><view class"my-config"><view class"config-item" tap"go…

精益项目管理的流程

我们生活在一个企业家的世界&#xff0c;您可能有许多自己的想法等待实现&#xff0c;但想法在现实中实现是昂贵的。问题是您如何才能获得最大的收益&#xff1f;CEO和管理者如何在追逐梦想和实现目标的同时节省资金&#xff1f;了解初创公司如何进行精益项目管理&#xff0c;它…

第一个汇编程序

第一个汇编程序 文章目录第一个汇编程序1.汇编模拟程序&#xff1a;DOSBox使用2.汇编程序从写出到执行的过程3.程序执行过程跟踪1.汇编模拟程序&#xff1a;DOSBox使用 BOSBox软件常用基本语法&#xff1a; mount c: d:\masn ;挂载磁盘,挂载后用c:切换为C盘才能用debug等工具…

【Java面试八股文宝典之基础篇】备战2023 查缺补漏 你越早准备 越早成功!!!——Day09

大家好&#xff0c;我是陶然同学&#xff0c;软件工程大三明年实习。认识我的朋友们知道&#xff0c;我是科班出身&#xff0c;学的还行&#xff0c;但是对面试掌握不够&#xff0c;所以我将用这100多天更新Java面试题&#x1f643;&#x1f643;。 不敢苟同&#xff0c;相信大…

uni-app入门:WXML列表渲染与条件渲染

1.列表渲染 1.1wx:for 1.2wx:key 2.条件渲染 2.1wx:if 2.2 hidden 正文 WXML全称&#xff1a;wexin markup language,微信标签语言&#xff0c;可以理解为web中的html&#xff0c;今天来讲一下列表渲染&#xff0c;通过几个小案例掌…

艾美捷高纯度 Cholesterol胆固醇相关介绍

胆固醇在体内有着广泛的生理作用&#xff0c;但当其过量时便会导致高胆固醇血症&#xff0c;对机体产生不利的影响。现代研究已发现&#xff0c;动脉粥样硬化、静脉血栓形成与胆石症与高胆固醇血症有密切的相关性。 如果是单纯的胆固醇高则饮食调节是最好的办法&#xff0c;如果…