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

2023-11-20

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 会这样觉得,自己确实偏向错了,根本就不该偏向。于是整个类的所有对象 都会变为不可偏向的,新建的对象也是不可偏向的

锁消除

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

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

JUC并发编程共享模型之管程(三)(中) 的相关文章

  • 读取文件并获取 key=value 而不使用 java.util.Properties

    我正在构建一个 RMI 游戏 客户端将加载一个包含一些键和值的文件 这些键和值将用于多个不同的对象 它是一个保存游戏文件 但我不能为此使用 java util Properties 它符合规范 我必须读取整个文件并忽略注释行和与某些类不相关
  • 如何防止在 CXF Web 服务客户端中生成 JAXBElement

    我正在尝试使用 CXF 创建一个 Web 服务客户端来使用 WCF Web 服务 当我使用 wsdl2java 时 它生成具有 JAXBElement 类型而不是 String 的对象 我读到有关使用 jaxb bindings xml 文
  • 在哈希图中存储字符和二进制数

    我正在尝试存储字母到二进制数的映射 这是我的映射 h 001 i 010 k 011 l 100 r 101 s 110 t 111 为此 我创建了一个哈希映射并存储了键值对 我现在想显示给定句子的相应二进制值 这是我的代码 package
  • JBoss AS 5 中的共享库应该放在哪里?

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

    我想知道我是否要在一个字符串上分割字符串 正确的方式 我的代码是 String fn filename split return fn 0 我只需要字符串的第一部分 这就是我返回第一项的原因 我问这个是因为我在 API 中注意到 意味着任何
  • 在 Android 中绘制一条带有弯曲边缘的线

    I am using canvas drawLine to draw some line in android but the lines are too sharp but i need a curved edges 这里的 1 是我所拥
  • FFmpeg 不适用于 android 10,直接进入 onFailure(String message) 并显示空消息

    我在我的一个项目中使用 FFmpeg 进行视频压缩 在 Android 10 Google Pixel 3a 上 对于发送执行的任何命令 它会直接进入 onFailure String message 并显示空消息 所以我在我的应用程序 g
  • 为什么一个线程会中断另一个线程[重复]

    这个问题在这里已经有答案了 在Java多线程应用程序中 我们处理InterruptedThreadException 如果另一个线程中断当前线程 则会抛出此异常 现在 当另一个线程知道它将导致异常时 它可能想要中断当前线程的原因是什么 很多
  • 如何在Gradle中支持多种语言(Java和Scala)的多个项目?

    我正在尝试将过时的 Ant 构建转换为 Gradle 该项目包含约50个Java子项目和10个Scala子项目 Java 项目仅包含 Java Scala 项目仅包含 Scala 每个项目都是由 Java 和 Scala 构建的 这大大减慢
  • JavaFx 中装饰且不可移动的舞台

    我想在 JavaFx 中创建一个装饰舞台 它也将不可移动 我正在从另一个控制器类创建这个阶段 我能够创造和展示舞台 但它是自由移动的 我怎样才能创建这个 非常感谢帮助和建议 我把打开新关卡的方法贴出来 private void addRec
  • 中间件 API 的最佳实践是什么? [关闭]

    Closed 此问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我们正在开发一个中间件 SDK 采用 C 和 Java 语言 供游戏开发人员 动画软件开发人员 阿凡达开
  • MessageDigest MD5 算法未返回我期望的结果

    我脑后的某个东西告诉我 我在这里遗漏了一些明显的东西 我正在将现有的 java 项目与第三方 api 集成 该第三方 api 使用 api 密钥的 md5 哈希进行身份验证 它对我不起作用 在调试过程中我意识到我生成的哈希值与他们提供的示例
  • 如何获取 Android 中临时文件的文件大小?

    如果我使用 openFileOutput 创建并写入临时文件 写入完成后如何获取文件大小 我希望这可以帮助你 File file new File selectedPath int file size Integer parseInt St
  • 尝试在空对象引用上调用虚拟方法“java.lang.String org.jsoup.nodes.Element.ownText()”

    我正在使用下面的代码来获取版本名称 from 应用商店通过使用 jsoup 我正在获取详细信息 但它引发了一些异常 我的代码是 public class ForceUpdateAsync extends AsyncTask
  • 更改 RowLayout SWT Java 中元素的顺序

    有没有办法更改在行布局中创建的元素的顺序 我想将其显示在元素中 首先显示 例如 如果我创建 element1 则 element2 element3 element4 我想看到的布局为 元素4 元素3 元素2 元素1 这意味着最后创建的元素
  • Netty中连接关闭后重新连接的最佳方法是什么

    简单场景 扩展 SimpleChannelUpstreamHandler 的较低级别的类 A 此类是发送消息和接收响应的主力 系统其他部分可以使用顶级类 B 来发送和接收消息 可以模拟同步和异步 此类创建 ClientBootstrap 设
  • NoSuchMethodError:将 Firebase 与应用程序引擎应用程序集成时

    我试图将 firebase 实时数据库与谷歌应用程序引擎应用程序集成 我在调用时收到此错误 gt DatabaseReference ref FirebaseDatabase gt getInstance gt getReference t
  • 膨胀类片段 InflateException 二进制 XML 文件时出错

    我正在使用 Material Design 和 NavigationDrawer 布局等设计我的第一个应用程序 但我遇到了一个问题 该应用程序非常简单 它只显示文本 并且基于 Android Studio 中提供的模板 尝试启动我的应用程序
  • 在方法内声明类 - Final 关键字 [重复]

    这个问题在这里已经有答案了 给定方法中的以下内部类 IsSomething public class InnerMethod private int x public class Something private int y public
  • Jenkins 管道和 java.nio.file.* 方法的问题

    我正在尝试使用 java nio file 中的方法在 Jenkins 管道中执行一些基本文件操作 无论代码存在于哪个节点块中 代码都在主节点上执行 在管道中 我已经验证了各个节点块都是正确的 它们唯一地标识了特定的节点 但是 pathEx

随机推荐

  • springboot 整合EHcache 实现分页缓存

    一 简要概述 Cacheable 对当前的对象做缓存处理 Cacheable 作用 把方法的返回值添加到 Ehcache 中做缓存 Cacheable value xxx key xxxx Value 属性 指定一个 Ehcache 配置文
  • 小米造车?年轻人的第一辆电动车?

    素来有着价格屠夫称号的 小米 终于要对电动车出手了 事件简讯 昨天下午 据 晚点LatePost 爆料 小米 已确定造车 并视其为战略级决策 不过具体形式和路径还未确定 或许仍有变数 一位知情人士称 小米造车或将由小米集团创始人雷军亲自带队
  • 软件质量保障之代码走查

    目的 代码走查有几个目的 第一个是让新同学快速熟悉代码并了解系统 第二个是做咨询防控的事前检查 避免引发线上故障 第三个是通过一起讨论和审查 加强团队代码阅读和编写能力 让大家编写出优秀的代码 代码走查的优点非常多 但是最核心的还是提前发现
  • 模2除法——用非常直观的例子解释

    前言 差错检测中有名唤CRC之方法 但很多学习者难以理解其运行原理 特别是模2除法 故博主将其原理以示例方式记录下来 以便同道稍作借鉴 因博主水平有限 难免会出现错误 希各位能多多包涵和给予建议 注意 本博客假设各位已理解CRC原理但对模2
  • javascript几个知识点

    本文总结一下javascript几个比较重要的知识点 包括scope chain this 和函数的一些高级特性 scope chain scope chain是javascript函数调用里最核心的概念 尤其是要理解闭包的概念的话 必须先
  • Unity中按钮检测鼠标状态

    改方法主要是用于按钮检测鼠标的进入 滑出 点击 抬起 长按 长按停止 1 先将下面这个脚本挂载到需要检测鼠标状态的按钮上 using System Collections using System Collections Generic u
  • 时序预测

    时序预测 MATLAB实现趋势外推时间序列预测 含移动平均 指数平滑对比 目录 时序预测 MATLAB实现趋势外推时间序列预测 含移动平均 指数平滑对比 基本介绍 程序设计 学习总结 参考资料 基本介绍 MATLAB实现趋势外推时间序列预测
  • 《银行法律法规》一、经济金融基础知识——4、银行体系

    第四章 银行体系 第一节 银行起源和发展 考点1 商业银行产生与发展 概念 商业银行指能够吸收公众存款 发放贷款 办理结算等多种业务 以盈利为主要经营目标 经营货币的金融企业 在银行体系中占有重要地位 信用活动中起着主导作用 产生途径 现代
  • mount通过NFS挂载

    文章目录 mount通过NFS挂载 1 NFS介绍 2 安装 1 ubuntu服务器安装命令 2 客户端linux5 4安装指令 3 建立NFS共享文件目录 4 配置NFS共享配置文件 1 第一段的目录需要替换成自己的共享文件目录 2 第二
  • C++ 高性能Http服务器 - HelloWorld(一)

    本文使用 newobj 跨平台开发框架实现 Web 服务的搭建及业务处理 最终实现个人博客网站的Demo 其中使用Mysql Redis数据库 该框架实测可处理 6w 并发的请求并进行数据库处理 非常适合工作或学习中需要了解或应用C 开发w
  • SO_RCVTIMEO ,  MSG_WAITALL

    test SO RCVTIMEO and MSG WAITALL 1 首先两者都运用于阻塞的情景下 对nonblock的fd不起作用 2 SO RCVTIMEO socket选项 作为getsockopt setsockopt的参数 见下
  • 【PTA】二叉树题总结

    完全二叉搜索树 中序遍历 存位置 一个无重复的非负整数序列 必定对应唯一的一棵形状为完全二叉树的二叉搜索树 本题就要求你输出这棵树的层序遍历序列 输入格式 首先第一行给出一个正整数 N 1000 随后第二行给出 N 个不重复的非负整数 数字
  • Hadoop的java程序报错Exception in thread "main" java.io.FileNotFoundException: File does not exis

    找了半天发现是因为路径没有写全 正确路径应该是E abc txt 注意检查路径是不是写全了 尤其是后缀
  • SourceInsight

    1 开胃菜 初级应用 1 1 选择美丽的界面享受工作 虽然不能以貌取人 但似乎从来没有人责备以貌取软件的 SI的华丽界面 绝对符合现代花花世界的人的审美趣味 在SI中 我们可以轻松地把各种类型关键字 变量 标志符 函数 宏 注释等定义为不同
  • NBA球星莱昂纳德年纪以及成就

    莱昂约翰 德 莱昂纳德 LeBron James 现年35岁 是美国职业篮球运动员 曾三度获得NBA总冠军 六次获得常规赛最有价值球员奖 MVP 五次获得联盟最佳防守球员奖 和入选了十五次全明星阵容 是NBA史上最伟大的球员之一
  • lpv4dns服务器怎么修改,超简单方法让电脑网速飞起来,只需这么做:更改DNS服务器地址!...

    今天教大教一个技巧 让电脑浏览器打开网页速度更快 一般电脑默认是自动获取IP地址跟DNS服务器 我们手动更改为谷歌DNS 这样电脑浏览器打开网页的速度会快很多 1 鼠标右击任务栏本地连接图标 点击打开网络和共享中心 2 点击更改适配器设置
  • 计算机网络,用Excel画3种编码方式图(非归零编码,曼彻斯特编码,差分曼彻斯特编码)

    计算机网络 用Excel画3种编码方式图 非归零编码 曼彻斯特编码 差分曼彻斯特编码
  • Java及数据库事务

    数据库并发问题 1 脏读 读取未提交数据 A事务读取B事务尚未提交的数据 此时如果B事务发生错误并执行回滚操作 那么A事务读取到的数据就是脏数据 就好像原本的数据比较干净 纯粹 此时由于B事务更改了它 这个数据变得不再纯粹 这个时候A事务立
  • golang:环境变量GOPROXY和GO111MODULE设置

    我们安装完golang后 我们在windows的cmd命令下就可以直接查看和使用go命令和环境变量了 同样的在linux下可以在控制台使用 如下图所示 C Users lijie1 gt go env set GO111MODULE set
  • JUC并发编程共享模型之管程(三)(中)

    4 5Monitor概念 Java 对象头 以 32 位虚拟机为例 在32位虚拟机中 1个机器码等于4字节 也就是32bit 在64位虚拟机中 1个机器码是8个字节 也就是64bit 普通对象 数组对象 其中Mark Word 结构为 最后