JUC源码分析:通过ReentrantLock阅读AbstractQueuedSynchronizer源码

news2024/11/29 2:30:59

一、概述

ReentrantLock进行上锁的流程如下图所示,我们将按照下面的流程分析ReentrantLock上锁的流程,在此过程中阅读AbstractQueuedSynchronizer源码。

 AQS 的数据结构如下图所示。 AQS大家还记得吗?最核心的是它的一个共享的int类型值叫做state,这个state用来干什么,其实主要是看他的子类是怎么实现的,比如ReentrantLock这个state是用来干什么的?拿这个state来记录这个线程到底重入了多少次,比如说有一个线程拿到state这个把锁了,state的值就从0变成了1,这个线程又重入了一次,state就变成2了,又重入一次就变成3等等,什么时候释放了呢?从3变成2变成1变成0就释放了,这个就是AQS核心的东西,一个数,这个数代表了什么要看子类怎么去实现它,那么在这个state核心上还会有一堆的线程节点,当然这个节点是node,每个node里面包含一个线程,我们称为线程节点,这么多的线程节点去争用这个state,谁拿到了state,就表示谁得到了这把锁,AQS得核心就是一个共享的数据,一堆互相抢夺竞争的线程,这个就是AQS。

二、源码阅读

 先进入ReentrantLock.lock方法。

 再进入内部类NonfairSync的lock方法。

 点击acquire方法进入AbstractQueuedSynchronizer.acquire方法。

 进入tryAcquire方法回到ReentrantLock.nonfairTryAcquire方法。

 

 再看另一个条件acquireQueued(addWaiter(Node.EXCLUSIVE)。AbstractQueuedSynchronizer.addWaiter方法将当前线程放入阻塞队列中。

 之后它会从队列中不断请求获取线程,直到获取为止。

三、拓展点

我们再来讲一个细节,我们看addWaiter()这个方法里边有一个node.setPrevRelaved(oldTail),这个方法的意思是把当前节点的前置节点写成tail,进入这个方法你会看到PREV.set(this,p),那这个PREV是什么东西呢?当你真正去读这个代码,读的特别细的时候你会发现,PREV有这么一个东西叫VarHandle,这个VarHandle是什么呢?这个东西实在JDK1.9之后才有的,我们说一下这个VarHandle,Var叫变量(variable),Handle叫句柄,打个比方,比如我们写了一句话叫Object o= new Object(),我们new了一个Object,这个时候内存里有一个小的引用“O”,指向一段大的内存这个内存里是new的那个Object对象,那么这个VarHandle指什么呢?指的是这个“引用”,我们思考一下,如果VarHandle代表“引用”,那么VarHandle所代表的这个值PREV是不是也这个“引用”呢?当然是了。这个时候我们会生出一个疑问,本来已经有一个“O”指向这个Object对象了,为什么还要用另外一个引用也指向这个对象,这是为什么?

//JDK源码
public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
	public final void acquire(int arg){
        //判断是否得到锁
		if(!tryAcquire(arg) 
           && acquireQueued(addWaiter(Node.EXCLUSIVE),arg))
       	 selfInterrupt();
	}
    
    private Node addWaiter(Node mode){
        //获取当前要加进来的线程的node(节点)
		Node node = new Node(mode);
        for(;;){
            //回想一下AQS数据结构图
		   Node oldTail = tail;
            if(oldTail != null){
                //把我们这个新节点的前置节点设置在等待队列的末端
				node.setPrevRelaved(oldTail);
                //CAS操作,把我们这个新节点设置为tail末端
                 if(compareAndAetTail(oldTail,node)){
					oldTail.next = node;
                      return node;
                 }
            }else{
                initializeSuncQueue();
            }
        }
    }
    
    final void setPrevRelaved(Node p){
        PREV.set(this,p);
    }
    
    private static final VarHandle PREV;
    static {
        try{
			MethodHandles.Lookup l = MethodHandles.lookup():
             PREV = l.findVarHandle(Node.class,"prev",Node.class);
        }catch(ReflectiveOperationException e){
            throw new ExceptionInInitializerError(e);
        }
    }
}

我们来看一个小程序,用这个小程序来理解这个VarHandle是什么意思,在这个类,我们定义了一个int类型的变量x,然后定义了一个VarHandle类型的变量handle,在静态代码块里设置了handle指向T01_HelloVarHandle类里的x变量的引用,换句话说就是通过这个handle也能找到这个x,这么说比较精确,通过这个x能找到这个x,里面装了个8,通过handle也能找到这个x,这样我们就可以通过这个handle来操作这个x的值,我们看main方法里,我们创建了T01_HelloVarHandle对象叫t,这个t对象里有一个x,里面还有个handle,这个handle也指向这个x,既然handle指向x,我当然可以(int)handle.get(t)拿到这个x的值不就是8吗?我还可以通过handle.set(t,9)来设置这个t对象的x值为9,读写操作很容易理解,因为handle指向了这个变量,但是最关键的是通过这个handle可以做什么事呢?handle.compareAndSet(t,9,10),做原子性的修改值,我通过handle.compareAndSet(t,9,10)把9改成10改成100,这是原子性的操作,你通过x=100 ,它会是原子性的吗?当然int类型是原子性的,但是long类型呢?就是说long类型连x=100都不是原子性的,所以通过这个handle可以做一些compareAndSet操作(原子操作),还可以handle.getAndAdd()操作这也是原子操作,比如说你原来写x=x+10,这肯定不是原子操作,因为当你写这句话的时候,你是需要加锁的,要做到线程安全的话是需要加锁的,但是如果通过handle是不需要的,所以这就是为什么会有VarHandle,VarHandle除了可以完成普通属性的原子操作,还可以完成原子性的线程安全的操作,这也是VarHandle的含义。

在JDK1.9之前要操作类里边的成员变量的属性,只能通过反射完成,用反射和用VarHandle的区别在于,VarHandle的效率要高的多,反射每次用之前要检查,VarHandle不需要,VarHandle可以理解为直接操纵二进制码,所以VarHandle反射高的多

//小程序
public class T01_HelloVarHandle {
    int x = 8;
    private static VarHandle handle;
    static {
        try {
            handle = MethodHandles.lookup().findVarHandle(T01_HelloVarHandle.class, "x", int.class);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        T01_HelloVarHandle t = new T01_HelloVarHandle();
        //plain read / write
        System.out.println((int)handle.get(t));
        handle.set(t,9);
        System.out.println(t.x);
        handle.compareAndSet(t, 9, 10);
        System.out.println(t.x);
        handle.getAndAdd(t, 10);
        System.out.println(t.x);

    }
}

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

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

相关文章

电脑自动关机是什么原因?如何解决?

案例:有时候我的电脑用着就突然关机,会导致一些没有保存的文件丢失。有没有小伙伴知道电脑为什么会自动关机?怎样做才能避免这个问题? 在使用电脑过程中,遇到电脑自动关机的问题是很常见的。当我们在进行重要任务时&a…

Netty核心源码剖析(四)

1.Netty心跳(heartbeat)服务源码剖析 1>.Netty作为一个网络框架,提供了诸多功能,比如编码解码等,Netty还提供了非常重要的一个服务–心跳机制heartbeat.通过心跳检查对方是否有效,这是RPC框架中是必不可少的功能.下面我们分析一下Netty内部心跳服务源码实现; 2>.Netty提…

电磁仿真需要牢记的内功心法

在射频、微波设计中,各种“强大”的商用电磁仿真软件的功能包罗万象,这篇“内功心法”从算法角度出发,提示大家如何谨慎选择仿真软件。 心法一:场”与“路”的区分 世上本无“路”,“场”近似得多了就变成了“路”&a…

千人规模亚马逊云科技出海日将于6月9日开启,助推企业出海出圈

向全球价值链上游奋进 中国企业增强国际竞争力的关键,是努力朝全球价值链上游奋进,发力技术出海。中国的出海新机遇,背后曾是疫情在全球按下数字互联和数字化升级的快进键,跨境电商、在线社交、移动支付、数字服务等数字经济迎来…

什么是 Vue.js 中的 keep-alive 组件?如何使用 keep-alive 组件?

Vue.js 中的 Keep-alive 组件 Vue.js 是一款流行的前端框架,它提供了许多实用的组件和工具,其中之一就是 Keep-alive 组件。Keep-alive 组件是 Vue.js 的一个高阶组件,它可以帮助我们缓存组件实例,提高应用程序的性能和响应速度。…

python3写一个http接口服务(get, post),给别人调用6

python3写一个http接口服务(get, post),给别人调用6 一、python3写一个http接口服务(get, post),给别人调用6 近年来异步web服务器比较火热,例如falcon/bottle/sanic/aiohttp,今天也来玩玩sanic。 Sanic是一个支持Python 3.7的w…

Vue.js 中的 v-for 中的 key 属性

Vue.js 中的 v-for 中的 key 属性 Vue.js 是一个流行的 JavaScript 前端框架,它提供了一种简单的方式来构建可复用的组件和应用程序。在 Vue.js 中,v-for 指令用于循环渲染一个数组或对象,并将每个元素渲染为一个 DOM 元素。在使用 v-for 指…

数据安全架构设计

在提到安全架构之前,我们先看看安全的定义:安全是产品的质量属性,安全的目标是保障产品里信息资产的保密性(Confidentiality)、完整性(Integrity)和可用性(Availability)…

【大数据学习篇14】centos6安装Mysql

目录 1. centos6.5安装mysql5版本 1.1 以su超级用户,安装Mysql数据库 1.2 启动Mysql数据库 1.3、安装Mysql客户端 1.4 进入Mysql 1.5 设置密码123456,展示所有数据库 1.6 进入数据库test 1.7 创建数据库表 1.8 重新输入密码123456,进…

java代码的freemarker模板将JSP页面转换成word文档导出

使用java代码的freemarker模板将JSP页面转换成word文档导出 使用java代码的freemarker模板将JSP页面转换成word文档导出 一、准备好freemarker模板, 我的模板是这样的 需要特别注意的是,这些名字的写法是很特殊的,这个模板是wps在进行word…

100种思维模型之放大关键行动思维模型-75

很多时候,决定结果大小的是 关键行动的执行程度, 所以我们要适时 放大关键行动 ! 放大关键行动思维模型,一个 告诉我们 事前思考寻找能够破局的关键点,落实时要放大关键点上的行动 的思维模型。 01、何谓放大关键行动…

《Kali渗透基础》06. 主动信息收集(三)

kali渗透 1:服务识别1.1:NetCat1.2:Socket1.3:dmitry1.4:nmap 2:操作系统识别2.1:Scapy2.2:nmap2.3:p0f 3:SNMP 扫描3.1:onesixtyone3.2&#xff…

AI初体验 - 最初的两次AI辅助开发

1.第一个尝试:物理公式 2023年6月7日,是我第一次在 AI(ChatGPT)的帮助下,完成了一个数据分析工作。当时手里有一些数据,我大致知道物理原理,但是无法给出一个合适的公式。我大概工作了5个小时,没有进展&am…

RTP介绍

一、简介 Real-time Transport Protocol(实时传输协议) 具体参考rfc3350介绍 可负载H264、H265、G711A、AAC、PS、私有流等各种数据。 二、格式 RTP 固定头部格式如下 version (V): 2bits RTP协议版本号 目前协议版本为2 padding (P): 1bit 填充位 设置成1&#xff…

无线led显示屏的优势

无线LED显示屏是一种利用无线技术进行数据传输和控制的LED显示屏,相比传统有线连接的LED显示屏,具有以下优势: 灵活性和便捷性:无线LED显示屏无需使用复杂的有线连接,可以通过无线网络进行数据传输和控制。这使得安装和…

Linux内核中内存管理相关配置项的详细解析1

本文基于kernel 6.1.0,针对于“Linux/x86 6.1.0 Kernel Configuration”中的“Memory Management options”项下的各个子配置项(如下图所示)进行详细解析。 一、Support for paging of anonymous memory (swap) 这个选项以前位于“General S…

deepin 安装 MySQL

1.下载网址:MySQL :: Begin Your Downloadhttps://dev.mysql.com/downloads/file/?id519241 不用注册,直接下载 2. 打开下载文件:mysql ......deb 文件 3 选择步骤:选ubuntu bionic 4 ->MySQL Server&Cluster->mysq…

钢铁废水除氟

钢铁工业废水含多种污染物,包括大量的挥发酚、氟化物、石油类、悬浮物、砷、铅等有害物质。其中含氟工业废水的大量排放,不仅污染环境,还会危害到农作物和牲畜的生长发育,并且可以通过食物链影响到人体健康。所以对含氟废水需降氟…

去掉字符串中的空格,通过正则根据不同的需求分别能去掉前、后、前后、中间的空格。

正则小tips: 正则表达式 - 修饰符 gglobal - 全局匹配 正则表达式 - 元字符 ^匹配输入字符串的开始位置。*匹配前面的子表达式零次或多次。$匹配输入字符串的结束位置。\s 匹配任何空白字符,包括空格、制表符、换页符等等。 匹配前面的子表达式一次…

【计算机组成与体系结构Ⅰ】章节测试(5-7)

在CPU中跟踪指令后继地址的寄存器是___。 A 主存地址寄存器 B 程序计数器 (我的答案) C 指令寄存器 D 状态条件寄存器 下面描述的RISC机器基本概念中,正确的表述是 __。 A. RISC机器不一定是流水CPU. B. RISC机器一定是流水CPU. &#…