JUC并发编程共享模型之管程(三)(中)

news2024/11/18 13:31:42

4.5Monitor概念

Java 对象头

以 32 位虚拟机为例(在32位虚拟机中,1个机器码等于4字节,也就是32bit,在64位虚拟机中,1个机器码是8个字节,也就是64bit)

普通对象

在这里插入图片描述

数组对象

在这里插入图片描述

其中Mark Word 结构为 : 最后两位是锁标志位

在这里插入图片描述

64位虚拟机 Mark Word

在这里插入图片描述

原理之 Monitor(锁)

Monitor 被翻译为 监视器管程

每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的 Mark Word 中就被设置指向 Monitor 对象的指针

结构:
在这里插入图片描述

  • 刚开始 Monitor 中 Owner 为null
  • 当Thread-2 执行 synchronized(obj) 就会将 Monitor 的所有者 Owner 置为Thread-2,Monitor中只能有有一个人Owner
  • 在Thread-2上锁过程中,如果Thread-3,Thread-4,Thread-5也来执行 synchronized(obj) ,就会进入EntryList BLOCKED
  • Thread-2执行完同步代码块的内容,然后唤醒 EntryList 中等待的线程来竞争锁,竞争是非公平的
  • 图中WaitSet中的Thread-0,Thread-1是之前获得过锁,但条件不满足进入WAITING状态的线程。
注意:
synchronized 必须是进入同一个对象的 monitor 才有上述的效果
不加 synchronized 的对象不会关联监视器,不遵从以上规则

4.6 原理synchronized

static final Object lock = new Object();
static int counter = 0;
public static void main(String[] args) {
    synchronized (lock) {
        counter++;
    }
}
public static void main(java.lang.String[]);
 	descriptor: ([Ljava/lang/String;)V
 	flags: ACC_PUBLIC, ACC_STATIC
    Code:
 		stack=2, locals=3, args_size=1
 		0: getstatic #2 // <- lock引用 (synchronized开始)
 		3: dup
 		4: astore_1 // lock引用 -> slot 1
 		5: monitorenter // 将 lock对象 MarkWord 置为 Monitor 指针
 		6: getstatic #3 // <- i
 		9: iconst_1 // 准备常数 1
 		10: iadd // +1
 		11: putstatic #3 // -> i
 		14: aload_1 // <- lock引用
 		15: monitorexit // 将 lock对象 MarkWord 重置, 唤醒 EntryList
		16: goto 24
 		19: astore_2 // e -> slot 2 
 		20: aload_1 // <- lock引用
 		21: monitorexit // 将 lock对象 MarkWord 重置, 唤醒 EntryList
 		22: aload_2 // <- slot 2 (e)
 		23: athrow // throw e
 		24: return
 	   Exception table:
 		from to target type
 		 6   16   19    any
        19   22   19    any
 	   LineNumberTable:
 		 line 8: 0
		 line 9: 6
		 line 10: 14
		 line 11: 24
       LocalVariableTable:
 		Start Length  Slot  Name  Signature
 		0       25     0    args  [Ljava/lang/String;
 	   StackMapTable: number_of_entries = 2
 		frame_type = 255 /* full_frame */
 			offset_delta = 19
		    locals = [ class "[Ljava/lang/String;", class java/lang/Object ]
 		    stack = [ class java/lang/Throwable ]
 		   frame_type = 250 /* chop */
 			offset_delta = 4

注意 方法级别的 synchronized 不会在字节码指令中有所体现

4.7 synchronize 锁

轻量级锁

轻量级锁的使用场景:如果一个对象虽然有多线程要加锁,但加锁的时间是错开的(也就是没有竞争),那么可以用轻量级锁来优化。

轻量级锁对使用者是透明的,即语法仍然是 synchronized

假设有两个方法同步块,利用同一个对象加锁

        static final Object obj = new Object();
        public static void method1() {
            synchronized( obj ) {
                // 同步块 A
                method2();
            }
        }
        public static void method2() {
            synchronized( obj ) {
                // 同步块 B
            }
        }
  • 创建锁记录(Lock Record) 对象,让每个线程的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的 Mark Word在这里插入图片描述

  • 让锁记录中 Object reference 指向锁对象,并尝试用 cas 替换 Object 的 Mark Word,将 Mark Word 的值存 入锁记录在这里插入图片描述

  • 如果 cas 替换成功,对象头中存储了 锁记录地址和状态 00 ,表示由该线程给对象加锁在这里插入图片描述

  • 如果 cas 失败,有两种情况

    • 如果是其他线程已经持有了该Object的轻量级锁,这是表明有竞争,进入锁膨胀过程(这里先不讨论,锁膨胀再说)
    • 如果是自己执行了synchronized锁重入,那么再添加一条Lock Record作为重入的计数在这里插入图片描述
  • 当退出 synchronized 代码块(解锁时) 如果有取值为null的锁记录,表示有锁重入,这时重置锁记录,表示重入计数-1在这里插入图片描述

  • 当退出 synchronized 代码块(解锁时) 所记录的值不为null,这时使用cas将 Mark Word 的值恢复给对象头

    • 成功,则解锁成功
    • 失败,则说明轻量级锁进入了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程

锁膨胀

​ 如果在尝试加轻量级锁的过程中,CAS操作无法成功,这时有一种情况就是有其他线程为此对象加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁

static Object obj = new Object();
	public static void method1() {
 		synchronized( obj ) {
 		// 同步块
	}
}
  • 当Thread-1进行轻量级加锁时,Thread-0已经对该对象加了轻量级锁在这里插入图片描述

  • 这是Thread-1加轻量级锁失败,进入了锁膨胀流程

    • 即为Object对象申请Monitor锁,让Object指向重量级锁地址
    • 然后自己进入Monitor的EntryList BLOCKED在这里插入图片描述
  • 当Thread-0退出同步块解锁时,使用CAS将Mark Word的值恢复给对象头,失败,这时会进入重量级解锁流程,即按照Monitor地址找到Monitor对象,设置Owner为null,唤醒EntryList中BLOCKED线程

自旋优化

重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步 块,释放了锁),这时当前线程就可以避免阻塞

自旋重试成功的情况:
在这里插入图片描述

自旋重试失败的情况:

  • 自旋会占用 CPU 时间,单核 CPU 自旋就是浪费,多核 CPU 自旋才能发挥优势。
  • 在 Java 6 之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会 高,就多自旋几次;反之,就少自旋甚至不自旋,总之,比较智能。
  • Java 7 之后不能控制是否开启自旋功能

偏向锁

  • 轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行CAS操作
  • Java6中引入偏向锁来做进一步优化:只有第一次使用CAS将线程ID设置到对象的Mark Word头,之后发现这个线程ID是自己的就表示没有竞争,不用重新CAS。以后只要不发生竞争,这个对象就归线程所有
        static final Object obj = new Object();
        public static void m1() {
            synchronized( obj ) {
                // 同步块 A
                m2();
            }
        }
        public static void m2() {
            synchronized( obj ) {
                // 同步块 B
                m3();
            }
        }
        public static void m3() {
            synchronized( obj ) {
                
                // 同步块 C
            }
        }

在这里插入图片描述

偏向状态

对象头格式

在这里插入图片描述

一个对象创建时:

  • 如果开启了偏向锁(默认开启),那么对象创建后,Mark Word 值为0x05即最后3位为101,这时它的thread、epoch、age都为0
  • 偏向锁默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加个VM参数 -XX:BisasedLockingStartupDelay=0 来禁用延迟
  • 如果没有开启偏向锁,那么对象创建后,Mark Word值为0x01 即最后3位为001,这时它的hashcode、age都为0,第一次用到 hashcode 时才会赋值

撤销偏向锁

使用hashcode()方法

调用了对象的hashCode,但偏向锁的对象Mark Word 中存储的是线程id,如果调用hashCode 会导致偏向锁被撤销

  • 轻量级锁会在所记录中记录 hashCode
  • 重量级锁会在 Monitor 中记录 hashCode

在调用 hashCode 后使用偏向锁,记得去掉 -XX:-UseBiasedLocking

其它线程使用对象

当有其它线程使用偏向锁对象时,会将偏向锁升级为轻量级锁

用 wait/notify

一个线程在加锁之前先wait。另一个线程先使用锁,等待锁用完之后,调用notify唤醒正在wait的线程,使得两个线程分开始用锁,在第二个线程使用锁前的时候,先是偏向第一个线程的(101),在使用锁的时偏向锁就失效了,转化为了000(轻量级锁),使用锁之后,输出的最后三位是001(偏向锁失效的状态,即正常状态)

批量重偏向

  • 如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程 T1 的对象仍有机会重新偏向 T2,重偏向会重置对象 的 Thread ID
  • 当撤销偏向锁阈值超过 20 次后,jvm 会这样觉得,我是不是偏向错了呢,于是会在给这些对象加锁时重新偏向至 加锁线程

批量撤销

当撤销偏向锁阈值超过 40 次后,jvm 会这样觉得,自己确实偏向错了,根本就不该偏向。于是整个类的所有对象 都会变为不可偏向的,新建的对象也是不可偏向的

锁消除

锁消除是指虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除。锁消除的主要判定依据来源于逃逸分析的数据支持,如果判断到一段代码中,在堆上的所有数据都不会逃逸出去被其他线程访问到,那就可以把它们当作栈上数据对待,认为它们是线程私有的,同步加锁自然就无须进行。

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

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

相关文章

2019年MathorCup数学建模C题汽配件制造业中的生产排程问题解题全过程文档及程序

2019年第九届MathorCup高校数学建模挑战赛 C题 汽配件制造业中的生产排程问题 原题再现&#xff1a; 整体求解过程概述(摘要) 随着市场竞争日趋激烈&#xff0c;企业开始更加注重低费高效&#xff0c;因此生产排程问题成为众多制造企业关注的热点之一。其中&#xff0c;制造行…

源码剖析Spring MVC如何将请求映射到Controller?

文章目录一、前言二、核心链路分析1、确定请求映射的入口1&#xff09;HandlerMapping注入Spring容器2&#xff09;HandlerMethod注册到MappingRegistry1> 判断Class是否为一个Handler2> 解析Class中的所有HandlerMethod 并注册到MappingRegistry中2、请求路径匹配1&…

【JavaScript速成之路】JavaScript函数

&#x1f4c3;个人主页&#xff1a;「小杨」的csdn博客 &#x1f525;系列专栏&#xff1a;【JavaScript速成之路】 &#x1f433;希望大家多多支持&#x1f970;一起进步呀&#xff01; 文章目录前言1&#xff0c;函数基础1.1&#xff0c;函数概念1.2&#xff0c;函数使用1.3&…

sHMIctrl 曲线控件使用

目录 效果 官方介绍 ​编辑 名词解释 使用方法 显示点 点在X轴位置 点在Y轴位置 量程 单位 如何设置自动标尺数据 设置量程 设置单位 效果 官方介绍 名词解释 四条曲线&#xff1a;同时最多使用4条曲线。 每条条曲线最多512点&#xff1a;X2-X1-Xn&#xff1b; 数据自定…

认识CSS值如何提高写前端代码的效率

&#x1f31f;所属专栏&#xff1a;前端只因变凤凰之路&#x1f414;作者简介&#xff1a;rchjr——五带信管菜只因一枚&#x1f62e;前言&#xff1a;该系列将持续更新前端的相关学习笔记&#xff0c;欢迎和我一样的小白订阅&#xff0c;一起学习共同进步~&#x1f449;文章简…

Spring注解开发之组件注册(一)

Spring注解开发 Spring注解开发之组件注册下半篇 IOC&#xff0c;中文名为控制反转&#xff0c;是将Java的bean对象存储在容器中&#xff0c;当需要使用时&#xff0c;通过名字获取该对象。而不是通过new关键字去创建。 1.Configuration & Bean给容器中注册组件 第一种&…

K_A16_003 基于STM32等单片机采集薄膜压力传感器参数串口与OLED0.96双显示

K_A16_003 基于STM32等单片机采集薄膜压力传感器参数串口与OLED0.96双显示一、资源说明二、基本参数参数引脚说明三、驱动说明对应程序:四、部分代码说明1、接线引脚定义STM32F103C8T6薄膜压力传感器模块五、基础知识学习与相关资料下载六、视频效果展示与程序资料获取七、注意…

Allegro如何在PCB中添加层面操作指导

Allegro如何在PCB中添加层面操作指导 在用Allegro做PCB设计的时候,根据需要,会在PCB中额外添加一些额外的层面,如下图 如何添加,具体操作如下 点击Setup点击Subclasses

实在智能RPA入选中国信通院《高质量数字化转型产品及服务全景图》

近日&#xff0c;中国信息通信研究院“高质量数字化转型创新发展大会暨中国信通院‘铸基计划’年度峰会”在北京召开&#xff0c;大会上信通院揭晓了《高质量数字化转型产品及服务全景图&#xff08;2022&#xff09;》&#xff08;以下称“全景图”&#xff09;。实在智能凭借…

自己定义typescript的类型声明文件xx.d.ts

****内容预警***菜鸟新手内容&#xff0c;大佬请绕道&#xff0c;不对的请指出我们在使用typescript的使用&#xff0c;如果安装一个包没有相应的类型声明文件&#xff0c;ts的类型检查就会报错&#xff0c;所以我们经常会安装npm包对应的types类型声明包&#xff0c;比如uuid …

手把手教你安装Linux!!!

文章目录Linux简述它们的区别安装CentOS①下载CentOS②安装Linux有两种方式③下载模拟软件④安装vmware⑤创建虚拟机⑥安装操作系统Linux简述 在国内比较流行的两款Linux发行版本CentOS和ubuntu 它们的区别 ubuntu&#xff1a;页面更加的华丽比较漂亮&#xff0c;它对计算机…

【bioinfo】融合检测软件FusionMap分析流程和报告结果

文章目录写在前面FusionMap融合检测原理FusionMap与其他软比较FusionMap分析流程FusionMap结果文件说明FusionMap mono CUP设置图片来源: https://en.wikipedia.org/wiki/Fusion_gene写在前面 下面主要内容是关于RNA-seq数据分析融合&#xff0c;用到软件是FusionMap 【Fusion…

连接微信群、Slack 和 GitHub:社区开放沟通的基础设施搭建

NebulaGraph 社区如何构建工具让 Slack、WeChat 中宝贵的群聊讨论同步到公共领域。 要开放&#xff0c;不要封闭 在开源社区中&#xff0c;开放的一个重要意义是社区内的沟通、讨论应该是透明、包容并且方便所有成员访问的。这意味着社区中的任何人都应该能够参与讨论和决策过…

编写程序:有92号和95号汽油可以选择,选择你需要的汽油,并输入需要加油的升数,点击按钮“`计算总价钱`“在div中可以得到你所需要支付的价格

需求&#xff1a; 有92号汽油和95号可以选择&#xff0c;选择你需要的汽油&#xff0c;并输入需要加油的升数&#xff0c;点击按钮"计算总价钱"在div中可以得到你所需要支付的价格。结构如下图所示&#xff1a; 详细代码如下&#xff1a; <!DOCTYPE html> &l…

图像识别技术OpenCV | C++版本

基础入门 图像与信号 图像 图像是人对视觉感知的物质再现。图像可以由光学设备获取&#xff0c;也可以人为创作。随着数字采集技术和信号处理理论的发展&#xff0c;越来越多的图像以数字形式存储。因而&#xff0c;有些情况下”图像“一词实际上是指数字图像。图像相关的话…

YOLOv8初体验:检测、跟踪、模型部署

安装 YOLOv8有两种安装方式&#xff0c;一种是直接用pip命令安装&#xff1a; pip install ultralytics另外一种是通过源码安装&#xff1a; git clone https://github.com/ultralytics/ultralytics cd ultralytics pip install -e .[dev]安装完成后就可以通过yolo命令在命令…

JavaScript的错误类型数据

在使用JavaScript开发过程中&#xff0c;当我们遇见浏览器控制台中出现的报错时&#xff0c;如何从这些错误类型快速定位到问题代码是一种必不可少的技能&#xff0c;下面我们来看看JavaScript的7种错误类型&#xff08;卷起来…&#xff09; 1、SyntaxError&#xff1a;语法错…

IP地址、网段处理模块IPy

【小白从小学Python、C、Java】【计算机等级考试500强双证书】【Python-数据分析】IP地址、网段处理模块IPy选择题以下关于python代码表述错误的一项是?from IPy import IPprint("【执行】IP(192.168.0.0/24).len()")print(IP(192.168.0.0/24).len())print("【…

realman——使用Moveit控制Gazebo中的机械臂

文章目录 概述新建工作区配置说明MoveIt端的配置机器人端的配置关节轨迹控制器关节状态控制器运行效果可能存在的问题概述 MoveIt!与 Gazebo 的联合仿真,其主要思路为搭建 ros_control 和 MoveIt!的桥梁。先在 MoveIt!端配置关节和传感器接口 yaml 文件,将其加载到 rviz 端;…

Java 某厂面试题真题合集

哈喽~大家好&#xff0c;这篇来看看Java 某厂面试题真题合集。 &#x1f947;个人主页&#xff1a;个人主页​​​​​ &#x1f948; 系列专栏&#xff1a;【日常学习上的分享】 &#x1f949;与这篇相关的文章&#xff1a; Spr…