Java之synchronized的JVM底层实现原理精简理解

1 synchronized的JVM底层原理实现的精简理解

Java 虚拟机中的synchronized基于进入和退出Monitor对象(也称为管程或监视器锁)实现, 无论是显式同步(synchronized作用在同步代码块,有明确的 monitorenter monitorexit 指令) 还是隐式同步synchronized作用在方法区,调用指令ACC_SYNCHRONIZED 标志)都是如此,都是使得Monitor对象里面的count计数期增加或者减少来实现,然后synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的Mutex Lock来实现的,而操作系统实现线程之间的切换时需要从用户态转换到核心态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的synchronized效率低的原因,ReentrantLock底层实现依赖于特殊的CPU指令,比如发送lock指令和unlock指令,不需要用户态和内核态的切换,所以效率高(这里和volatile底层原理类似)。

 

 

 

 

 

 

 

 

 

 

2 Java对象头与Monitor的理解

1)Java对象的构成

在JVM中,对象在内存中的布局分为三块区域:对象头实例数据对齐填充

  • 实例变量:类的属性数据信息,包括父类的属性信息。
  • 填充数据:虚拟机要求对象起始地址必须是8字节的整数倍,这里和C语言的结构体内存对齐还是有点不一样。
  • 对象头:jvm中采用2个字节来存储对象头(如果对象是数组则会分配3个字,多出来的1个字记录的是数组长度),其主要结构是由Mark Word Class Metadata Address 

虚拟机位数          头对象结构                              作用
32/64bit               Mark Word                               存储对象的hashCode锁信息分代年龄GC标志等信息
32/64bit               Class Metadata Address         类型指针指向对象的类元数据,JVM通过这个指针确定该对象是哪个类的实例。

 

32位JVM的Mark Word默认存储结构如下

锁状态25bit4bit1bit是否是偏向锁2bit 锁标志位
无锁状态对象HashCode对象分代年龄001

Mark Word默认存储结构外,还有如下可能变化的结构

 

 

 

 

 

 

2)monitor对象

轻量级锁和偏向锁是Java 6 对 synchronized 锁进行优化后新增加,重量级锁也就是通常说synchronized的对象锁,锁标识位为10,其中指针指向的是monitor对象,每个对象都存在着一个 monitor 与之关联,对象与其 monitor 之间的关系有存在多种实现方式,如monitor可以与对象一起创建销毁或当线程试图获取对象锁时自动生成,但当一个 monitor 被某个线程持有后,它便处于锁定状态。在Java虚拟机(HotSpot)中,monitor是由ObjectMonitor实现的,其主要数据结构如下(位于HotSpot虚拟机源码ObjectMonitor.hpp文件,C++实现的)
 

ObjectMonitor() {_header       = NULL;_count        = 0; //记录个数_waiters      = 0,_recursions   = 0;_object       = NULL;_owner        = NULL;_WaitSet      = NULL; //处于wait状态的线程,会被加入到_WaitSet_WaitSetLock  = 0 ;_Responsible  = NULL ;_succ         = NULL ;_cxq          = NULL ;FreeNext      = NULL ;_EntryList    = NULL ; //处于等待锁block状态的线程,会被加入到该列表_SpinFreq     = 0 ;_SpinClock    = 0 ;OwnerIsThread = 0 ;}

ObjectMonitor中有两个队列,_WaitSet 和 _EntryList,用来保存ObjectWaiter对象列表( 每个等待锁的线程都会被封装成ObjectWaiter对象),_owner指向持有ObjectMonitor对象的线程,当多个线程同时访问一段同步代码时,首先会进入 _EntryList 集合,当线程获取到对象的monitor 后进入 _Owner 区域并把monitor中的owner变量设置为当前线程同时monitor中的计数器count加1若线程调用 wait() 方法,将释放当前持有的monitor,owner变量恢复为null,count自减1,同时该线程进入 WaitSe t集合中等待被唤醒。若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor(锁)

monitor对象存在于每个Java对象的对象头中(存储的指针的指向),synchronized锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因,同时也是notify/notifyAll/wait等方法存在于顶级对象Object中的原因

 

 

 

 

 

 

 

 

 

 

3 synchronized作用于代码块

 synchronized作用代码块后反编译的字节码关键如下

3: monitorenter  //进入同步方法
//..........省略其他  
15: monitorexit   //退出同步方法
16: goto          24
//省略其他.......
21: monitorexit //退出同步方法

从字节码中可知同步语句块的实现使用的是monitorenter 和 monitorexit 指令,其中monitorenter指令指向同步代码块的开始位置,monitorexit指令则指明同步代码块的结束位置,当执行monitorenter指令时,当前线程将试图获取 objectref(即对象锁) 所对应的 monitor 的持有权,当 objectref 的 monitor 的进入计数器为 0,那线程可以成功取得 monitor,并将计数器值设置为 1,取锁成功。如果当前线程已经拥有 objectref 的 monitor 的持有权,那它可以重入这个 monitor (关于重入性稍后会分析),重入时计数器的值也会加 1。倘若其他线程已经拥有 objectref 的 monitor 的所有权,那当前线程将被阻塞,直到正在执行线程执行完毕,即monitorexit指令被执行,执行线程将释放 monitor(锁)并设置计数器值为0 ,其他线程将有机会持有 monitor 。值得注意的是编译器将会确保无论方法通过何种方式完成,方法中调用过的每条 monitorenter 指令都有执行其对应 monitorexit 指令,而无论这个方法是正常结束还是异常结束。为了保证在方法异常完成时 monitorenter 和 monitorexit 指令依然可以正确配对执行,编译器会自动产生一个异常处理器,这个异常处理器声明可处理所有的异常,它的目的就是用来执行 monitorexit 指令。从字节码中也可以看出多了一个monitorexit指令,它就是异常结束时被执行的释放monitor 的指令
 

 

 

 

 

 

 

 

 

 

 

4 synchronized作用于方法

synchronized作用代码块后反编译的字节码关键如下

 descriptor: ()V//方法标识ACC_PUBLIC代表public修饰,ACC_SYNCHRONIZED指明该方法为同步方法flags: ACC_PUBLIC, ACC_SYNCHRONIZEDCode:

 JVM可以从方法常量池中的方法表结构(method_info Structure) 中的 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法。当方法调用时,调用指令将会 检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先持有monitor(虚拟机规范中用的是管程一词), 然后再执行方法,最后再方法完成(无论是正常完成还是非正常完成)时释放monitor。在方法执行期间,执行线程持有了monitor,其他任何线程都无法再获得同一个monitor。如果一个同步方法执行期间抛 出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的monitor将在异常抛到同步方法之外时自动释放
 

 

 

 

 

 

 

 

 

 

 

 

5 synchronized的优化

锁的状态总共有四种,无锁状态偏向锁轻量级锁重量级锁

其膨胀方向:无锁——>偏向锁——>轻量级锁——>重量级锁,并且膨胀方向不可逆。 

1)  偏向锁:

核心思想是,如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word 的结构也变为偏向锁结构,当这个线程再次请求锁时,无需再做任何同步操作,即获取锁的过程这样就省去了大量有关锁申请的操作,从而也就提供程序的性能,对于没有锁竞争的场合,偏向锁有很好的优化效果,毕竟极有可能连续多次是同一个线程申请相同的锁。但是对于锁竞争比较激烈的场合,偏向锁就失效了,因为这样场合极有可能每次申请锁的线程都是不相同的,因此这种场合下不应该使用偏向锁,否则会得不偿失,需要注意的是,偏向锁失败后,并不会立即膨胀为重量级锁,而是先升级为轻量级锁。下面我们接着了解轻量级锁
 

2)  轻量级锁:

对绝大部分的锁,在整个同步周期内都不存在竞争”,注意这是经验数据。需要了解的是,轻量级锁所适应的场景是线程交替执行同步块的场合如果存在同一时间访问同一锁的场合,就会导致轻量级锁膨胀为重量级锁

 

 

 

3) 、重量级锁

重量级锁是由轻量级锁升级而来,同一时间有多个线程竞争锁时,锁就会被升级成重量级锁,此时其申请锁带来的开销也就变大

 

 

 

4) 、锁消除

消除锁是虚拟机另外一种锁的优化,这种优化更彻底,在JIT编译时,对运行上下文进行扫描,去除不可能存在竞争的锁

部分参考博客:https://blog.csdn.net/javazejian/article/details/72828483

 

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

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

相关文章

三分钟掌握Actor和CSP模型

点击上方蓝字进行关注前文传送门:《三分钟掌握共享内存模型和 Actor模型》, 一直想比较Actor模型与golang的CSP模型,经过一段时间的实战记录了本文。Actor vs CSP模型• 传统多线程的的共享内存(ShareMemory)模型使用l…

DateTimeToUnix/UnixToDateTime 对接时间转换

问题&#xff0c;通过毫秒数来解析出时间&#xff1a;&#xff08;很多对接的时候经常需要用到&#xff09; <?php $MyJson {"jingdong_vas_subscribe_get_responce":{"code":"0","item_code":"FW_GOODS-2236-1","…

【学生选课系统经典】VB与SQLSERVER连接:Windows应用工程案例

实验任务描述 1 用VB6访问SQLSERVER数据库(两种安全模式); 2 用VB6完成数据库指定表上的数据显示; 3 用VB6完成数据库指定表上的数据插入、删除和更新; 4 用VB6完成SQLSERVER2008数据库用户验证。 一、数据库系统 该实验中,所要求的数据库名称为SCHOOL,总共涉及以下表:

丢失api-ms-win-crt-runtime-l1-1-0.dll

运行Cmder的时候提示&#xff1a;丢失api-ms-win-crt-runtime-l1-1-0.dll在网上找了一些方法&#xff0c;基本解决方法都是装VC2015的运行时&#xff0c;但是我安装的时候出错&#xff0c;大家可以先试试。接着我就去解决安装出错这问题没&#xff0c;折腾了半天也没成功。后来…

《假如编程是魔法之零基础看得懂的Python入门教程 》——(二)魔法实习生第一步了解魔杖的使用

学习目标 了解什么是开发环境了解python语言的环境安装了解python语言编程的编辑器工具 目录 第一篇&#xff1a;《假如编程是魔法之零基础看得懂的Python入门教程 》——&#xff08;一&#xff09;既然你选择了这系列教程那么我就要让你听得懂 第三篇&#xff1a;《假如编…

Java之synchronized可重入性的理解

1 synchronized可重入性的理解 当一个线程试图操作一个由其他线程持有的对象锁的临界资源时&#xff0c;将会处于阻塞状态&#xff0c;但当一个线程再次请求自己持有对象锁的临界资源时&#xff0c;如果当前锁是重入性&#xff0c;会请求将会成功&#xff0c;如果当前锁不是可…

onmouseover-onmouseout

<input type"checkbox" value"autoLogin" οnmοuseοver"block()" οnmοuseοut"none()">两周内自动登录 <div id"div1">为了您的信息安全请不要在网吧或公共电脑勾选此项</div> <script> functi…

mysql5.7 only_full_group_by_Mysql5.7及以上版本 ONLY_FULL_GROUP_BY报错的解决方法

近期在开发过程中&#xff0c;因为项目开发环境连接的mysql数据库是阿里云的数据库&#xff0c;而阿里云的数据库版本是5.6的。而测试环境的mysql是自己安装的5.7。因此在开发过程中有小伙伴不注意写了有关group by的sql语句。在开发环境中运行是正常的&#xff0c;而到了测试环…

一款高速的NET版的离线免费OCR

PaddleOCR.Onnx一款基于Paddle的OCR&#xff0c;项目使用ONNX模型&#xff0c;速度更快。本项目同时支持X64和X86的CPU上使用。本项目是一个基于PaddleOCR的C代码修改并封装的.NET的工具类库。包含文本识别、文本检测、基于文本检测结果的统计分析的表格识别功能&#xff0c;同…

spring 注解简单使用

一、通用注解 1、项目结构&#xff1a; 2、新建Person类&#xff0c;注解Component未指明id&#xff0c;则后期使用spring获取实例对象时使用默认id"person"方式获取或使用类方式获取 package hjp.spring.annotation.commen;import org.springframework.stereotype.C…

selenium+python笔记3

#!/usr/bin/env python # -*- coding: utf-8 -*- """ desc:学习unittest的用法 注意setUp/setUpClass&#xff0c;tearDown/tearDownClass的区别 ① setUp():每个测试函数运行前运行 ② tearDown():每个测试函数运行完后执行 ③ setUpClass():必须使用classmeth…

【学生选课系统经典】C#与SQLSERVER连接:ASP.NET网站(服务器端,IIS发布)

实验任务描述 1 用C#访问SQLSERVER数据库(两种安全模式); 2 用C#完成数据库指定表上的数据显示; 3 用C#完成数据库指定表上的数据插入、删除和更新; 4 用C#完成数据库用户验证。 此处使用ASP.NET工程来完成这个项目,和Windows应用不同的是:这个项目是在服务器上、依靠IIS服…

TCP包头、UDP包头、IP包头、和MAC帧包头详细字段和包头大小

1 TCP头 TCP是一种可靠的、面向连接的字节流服务,头部定义如下。 /*TCP头定义,共20个字节*/ typedef struct _TCP_HEADER {short m_sSourPort;       // 源端口号16bitshort m_sDestPort;       // 目的端口号16bitunsigned int m_uiSequNum; …

经典面试题:用户反映你开发的网站访问很慢可能会是什么原因

原文链接&#xff1a;http://blog.csdn.net/lv_victor/article/details/53148421 问题场景&#xff1a;某个用户向你反映说你开发的网站访问速度很慢&#xff0c;但是该用户访问其他问题很正常&#xff0c;分析下原因、有哪些工具分析原因、怎么解决问题&#xff1f; 最近面试两…

《假如编程是魔法之零基础看得懂的Python入门教程 》——(三)使用初始魔法跟编程魔法世界打个招呼吧

学习目标 完成显示魔法的使用——输出print完成传入魔法的使用——输入input使魔法生效——运行python文件 目录 第一篇&#xff1a;《假如编程是魔法之零基础看得懂的Python入门教程 》——&#xff08;一&#xff09;既然你选择了这系列教程那么我就要让你听得懂 第二篇&am…

查缺补漏系统学习 EF Core 6 (一)

推荐关注「码侠江湖」加星标&#xff0c;时刻不忘江湖事掌握 ORM 开发方式是每一个 .NET 开发者所必备的技能&#xff0c;而且 .NET 平台有很多优秀的 ORM 框架。很多人都会诟病 .NET 官方标配的 Entity Framework&#xff0c;感觉其笨重难用、性能低下。但其实经过多年发展&am…

mysql 5.5 mysqldump_mysql 5.5 mysqldump 原文翻译

根据mysql 5.5第6.4章节理解和自己翻译水平有限如有纰漏请指教,原文如下.6.4 使用mysqldump备份(Using mysqldump for Backups)首先多余的不用说了备份用来干什么大家都清楚。mysqldump备份分两种输出形式&#xff1a;1. 无--tab选项&#xff0c;输出标准的SQL格式。输出包含CR…

【经典回放】JavaScript学习详细干货笔记之(一)

【经典回放】JavaScript学习详细干货笔记之&#xff08;一&#xff09; 【经典回放】JavaScript学习详细干货笔记之&#xff08;二&#xff09; 【经典回放】JavaScript学习详细干货笔记之&#xff08;三&#xff09; 目录 一、为什么要学JavaScript 二、JavaScript经典案例 …

Java Attach API

catalog 1. instrucment与Attach API 2. BTrace: VM Attach的两种方式 3. Sun JVM Attach API 1. instrucment与Attach API JDK5中增加了一个包java.lang.instrucment&#xff0c;能够对JVM底层组件进行访问。在JDK 5中&#xff0c;Instrument 要求在运行前利用命令行参数或者系…

TCP之三次握手和四次挥手过程

1 TCP包头里面的标志位 下图为TCP头部里面部分信息,入下标志位,每个标志位占一位。 标志位这里会涉及3个,ACK SYN FIN ACK:确认序号有效。 SYN:发起一个新连接。 FIN:释放一个连接。 2 三次握手过程 第一次握手 Client将标志位SYN置1,随机产生一个值seq=J,并将数…