【Java并发】深入浅出 synchronized关键词原理-下

上一篇文章,简要介绍了syn的基本用法和monter对象的结构,本篇主要深入理解,偏向锁、轻量级锁、重量级锁的本质。

对象内存布局

Hotspot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头(Header)、实例数据 (Instance Data)和对齐填充(Padding)。

对象头:比如 hash码,对象所属的年代,对象锁,锁状态标志,偏向锁(线程)ID, 偏向时间,数组长度(数组对象才有)等。
实例数据:存放类的属性数据信息,包括父类的属性信息;
对齐填充:由于虚拟机要求 对象起始地址必须是8字节的整数倍。填充数据不是必须存 在的,仅仅是为了字节对齐。

对象头详解

HotSpot虚拟机的对象头包括:

  • Mark Word 用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据的长度在32位和64位的虚拟机中分别为 32bit和64bit,官方称它为“Mark Word”。
  • Klass Pointer
    对象头的另外一部分是klass类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指 针来确定这个对象是哪个类的实例。 32位4字节,64位开启指针压缩或最大堆内存<32g时4字 节,否则8字节。jdk1.8默认开启指针压缩后为4字节,当在JVM参数中关闭指针压缩(-XX:- UseCompressedOops)后,长度为8字节。
  • 数组长度(只有数组对象有)
    如果对象是一个数组, 那在对象头中还必须有一块数据用于记录数组长度。 4字节

在这里插入图片描述
如何查看java对象信息数据

        <dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.9</version>
        </dependency>
    public static void main(String[] args) {
        Object o = new Object();
        System.out.println(ClassLayout.parseInstance(o).toPrintable());
    }

可以发现一个Object在压缩情况下是16字节。对其填充4字节。

 OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
      0     4        (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
     12     4        (loss due to the next object alignment)

思考题:markword如何记录锁状态的?

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
这里有一个问题,那就是为什么需要锁升级 在于原来的syn的锁比较重,每次wait\notify都需要从用户态到内核态的转换。而利用偏向锁和轻量级锁,可以在用户态没有竞争或者两个线程竞争的前提下进行锁竞争,避免频繁切换到内核态。你看本质上还是为了提高锁的性能。

偏向锁

偏向锁是一种针对加锁操作的优化手段,经过研究发现,在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,因此为了消除数据在无竞争情况下锁重入(CAS操 作)的开销而引入偏向锁。对于没有锁竞争的场合,偏向锁有很好的优化效果。

偏向锁延迟

偏向锁模式存在偏向锁延迟机制:HotSpot 虚拟机在启动后有个 4s 的延迟才会对每个新建 的对象开启偏向锁模式。JVM启动时会进行一系列的复杂活动,比如装载配置,系统类初始化等 等。在这个过程中会使用大量synchronized关键字对对象加锁,且这些锁大多数都不是偏向锁。 为了减少初始化时间,JVM默认延时加载偏向锁。

 //关闭延迟开启偏向锁XX:BiasedLockingStartupDelay=0 3 //禁止偏向锁XX:UseBiasedLocking
//启用偏向锁XX:+UseBiasedLocking
     System.out.println(Thread.currentThread().getName()+"\t"+ClassLayout.parseInstance(new Object()).toPrintable());
     TimeUnit.SECONDS.sleep(4);
      System.out.println(Thread.currentThread().getName()+"\t"+ClassLayout.parseInstance(new Object()).toPrintable());

在这里插入图片描述
偏向锁

    public static void main(String[] args) throws InterruptedException {
        TimeUnit.SECONDS.sleep(4);
        Object obj = new Object();

        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"\t"+ClassLayout.parseInstance(obj).toPrintable());
            synchronized (obj) {
                System.out.println(Thread.currentThread().getName()+"\t"+ClassLayout.parseInstance(obj).toPrintable());
            }
            System.out.println(Thread.currentThread().getName()+"\t"+ClassLayout.parseInstance(obj).toPrintable());
        },"T1").start();

在这里插入图片描述
**流程:**可以发现,当一个对象刚被创建的时候,markword处于无锁状态,并随即进入偏向锁状态,此时mark word字段中的threadI为0,此时线程获取某个对象的syn关键字的时候,会发现这个对象时处于可偏向的,101的。并且threadId为0.就会通过cas原子操作竞争这个偏向锁。markword为5 (101) 如果cas成功,则将当前线程的id设置进去。
但是发现在执行完,syn代码块的时候,发现并没有退出偏向锁。原因在于偏向锁不会主动释放,主要是提高加锁的效率。当这个线程再次获取syn对象锁的时候,可以判断markword是否偏向以及thredid是不是自己的线程id 做处理,如果是的话,直接可以使用。并且CAS操作其实是硬件层面的操作,
在这里插入图片描述

轻量级锁

在一个线程获取锁的时候,会设置为偏向锁,但是当两个线程交替执行的时候,会从偏向锁升级为轻量级锁。轻量级锁所适应的场景是线程交替执行同步块的场合,如果存在同一时间多个线程访问同一把锁的场合,就会导致轻量级锁膨胀为重 量级锁。

 TimeUnit.SECONDS.sleep(4);
        Object obj = new Object();

        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"\t"+ClassLayout.parseInstance(obj).toPrintable());
            synchronized (obj) {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"\t"+ClassLayout.parseInstance(obj).toPrintable());
            }},"T1").start();


        Thread.sleep(1000);

        new Thread(()->{
            synchronized (obj) {
                System.out.println(Thread.currentThread().getName()+"\t"+ClassLayout.parseInstance(obj).toPrintable());
            }
            System.out.println(Thread.currentThread().getName()+"\t"+ClassLayout.parseInstance(obj).toPrintable());
        },"T2").start();

在这里插入图片描述
从图中可以看出,锁一开始是可偏向,线程T1cas获取到偏向锁,但是线程T2没有获取到,自己CAS失败,偏向锁不会主动释放锁,因此在升级偏向锁时,虚拟机需要暂停持有偏向锁的线程,查看是否还在使用这个偏向锁,如果不在使用,那么就会将markword设置为无锁状态,如果这个锁还在使用,那就升级为轻量级锁。

好了我们来思考几个问题
为什么需要在将偏向锁升级时需要暂停偏向锁?
这是因为虚拟机需要根据持有偏向锁的线程是否正在使用偏向锁,来决定将偏向锁转为无锁还是轻量级锁,其实检查这类也是复合操作。因为是两个不同的线程操作,虚拟机线程可能检查到没有使用偏向锁,但是过了一会线程获取到偏向锁。显然无法将偏向锁设置为无锁状态,所以需要暂停持有偏向锁的线程。如何暂停就直接使用gc中的STW(stop the word)

能否不把偏向锁线程状态会退回偏向状态
其实如果出现线程竞争,会退到可偏向状态,可能会导致频繁的STW,所以还不如回退到无锁状态。
在这里插入图片描述
市面上很多的资料都是锁升级就是无锁->偏向锁-》轻量级锁->重量级锁。 其实是不准确的。正确的其实是一开始是偏向锁状态,根据偏向锁的是否持有线程判断,如果没有持有就升级到无锁状态,如果持有锁,并且还有一个线程竞争的前提下,那就升级到轻量级锁。如果竞争更加激烈,升级到重量级锁。但是升级到重量级锁后是无法降级的。
在这里插入图片描述

在这里插入图片描述

重量级锁

在上述代码的基础上,在加一个线程获取,就可以获得此效果。
在这里插入图片描述

锁消除

除了syn的锁升级,syn还有两个优化,那就是锁消除和锁粗化,虚拟机在JIT编译时,会根据代码的分析,去掉没有必要的锁,在多线程操作的安全性,StringBuffer中的append函数 设计实现时加了锁,在下面代码中,strBuffer是局部变量,不会被多线程共享,更不后在多线程环境下调用它的append函数,append函数的锁就可以被优化消除。
在这里插入图片描述

锁粗化

锁组化,其实也是在JIT编译的时候,根据锁的范围,将多个小的锁范围,调整为一个。比如如下,就是将1W次的append加解锁,粗化成一次。
在这里插入图片描述

总结

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Baumer工业相机堡盟工业相机如何联合NEOAPI SDK和OpenCV实现相机图像转换为Mat图像格式(C++)

Baumer工业相机堡盟工业相机如何通过NEOAPI SDK实现相机掉线自动重连&#xff08;C&#xff09; Baumer工业相机Baumer工业相机的图像转换为OpenCV的Mat图像的技术背景在NEOAPI SDK里实现相机图像转换为Mat图像格式联合OpenCV实现相机图像转换为Mat图像格式测试演示图 工业相机…

页面自适应postcss-pxtorem配置

一、项目背景 uniappvue3tsvitepiniavant 二、安装postcss-pxtorem npm i postcss-pxtorem三、vite.config.ts配置 import pxtorem from "postcss-pxtorem";export default defineConfig({plugins: [uni(),Components({resolvers: [VantResolver()]})],css: {pos…

【Java集合篇】HashMap 在 get 和 put 时经过哪些步骤

HashMap在get和put时经过哪些步骤? ✔️ 典型解析✔️get方法✔️put方法✔️ 拓展知识仓✔️ HashMap如何定位key✔️ HashMap定位tablelndex的骚操作作✔️HashMap的key为null时&#xff0c;没有hashCode是如何存储的?✔️ HashMap的value可以为null吗? 有什么优缺点讷? …

ZkSync第一Dex空投交互全教程,Holdstation ZK热点不容错过

2023 年 12 月 8 日&#xff0c;在以太坊基金会的 176 次会议上&#xff0c;开发人员一致同意&#xff0c;如果事情进展顺利&#xff0c;将在 2024 年初定 Goerli 分叉日期&#xff0c;目标是能在 2024 年 1 月激活 Goerli Dencun 测试网&#xff0c;预计能够在 2024 年 3 月~ …

SOFA Framework源代码及插件Win11编译开发环境配置

这篇文章主要记录详细的SOFA Framework软件的源代码编译环境配置过程&#xff0c;开发环境基于Win系统&#xff0c;编译完成后&#xff0c;可以在插件或框架的源代码上进行开发集成。本文纯手写输入&#xff0c;言简意赅&#xff0c;以大方向和思路为准&#xff0c;具体需要注意…

torch.meshgrid和np.meshgrid的区别

numpy中meshgrid&#xff1a; 把数组a当作一行&#xff0c;再根据数组b的长度扩充行。 把数组b当作一列&#xff0c;再根据数组a的长度扩充列。 torch中meshgrid&#xff1a; 把数组a当作一列&#xff0c;再根据数组b的长度扩充列。 把数组b当作一行&#xff0c;再根据数组a的…

前端 js 基础对象 (3)

js 对象定义 <!DOCTYPE html> <html> <body><h1>JavaScript 对象创建</h1><p id"demo1"></p> <p>new</p> <p id"demo"></p><script> // 创建对象&#xff1a; var persona {fi…

C++ 释放指针

在C中&#xff0c;释放指针通常使用delete或delete[]操作符&#xff1b; 如果指针指向的是单个对象&#xff0c;可以使用delete操作符进行释放&#xff1b; 在释放完内存后&#xff0c;最好将指针置为nullptr&#xff0c;以避免出现悬空指针&#xff08;dangling pointer&#…

LeGO-LOAM 几个特有函数的分析(2)

接上回LeGO-LOAM 几个特有函数的分析&#xff08;1&#xff09; 二、广度优先遍历 广度优先遍历&#xff08;Breadth-First Search, BFS&#xff09;是一种用于遍历或搜索树或图的算法。这种算法从树的根&#xff08;或图的某一指定节点&#xff09;开始&#xff0c;然后探索…

「小明赠书活动」2024第二期《实战AI大模型》

⭐️ 赠书 - 《实战AI大模型》 从基本概念到实践技巧的&#xff0c;全方位解读AI大模型&#xff0c;手把手教你训练和部署BERT、GPT-3、ChatGPT&#xff01; 人工智能领域资深专家尤洋老师倾力打造&#xff0c;获得了 李开复、周鸿祎、颜水成 三位大咖鼎力推荐&#xff0c;一经…

【前端】[vue3] vue-router使用

提示&#xff1a;我这边用的是typeScript语法&#xff0c;所以js文件的后缀都是ts。 安装vue-router&#xff1a; &#xff08;注意&#xff1a;vue2引用vue-router3 vue3才引用vue-router4&#xff09; npm install vue-router4src文件夹下面创建 router/index.ts&#xff08;…

安科瑞微机综合保护测控装置在某电厂10.5kV厂用电系统改造中的应用——安科瑞 顾烊宇

摘要&#xff1a;某电厂8号机10.5kV厂用电二次系统设备大多为常规电磁式继电器、电量变送器等。通过对厂用电二次系统从设备选型、设计、施工调试等方面进行的改造&#xff0c;尤其微机综合保护测控装置的应用&#xff0c;集控制、保护、测量、信号报警、开关量采集、通讯功能于…

Python+Appium自动化测试的使用步骤

这篇文章主要介绍了PythonAppium实现自动化测试的使用步骤&#xff0c;文中通过示例代码介绍的非常详细&#xff0c;对大家的学习或者工作具有一定的参考学习价值&#xff0c;需要的朋友们下面随着小编来一起学习学习吧 一、环境准备 1.脚本语言&#xff1a;Python3.x IDE&am…

IIS回收应用

前言 作为Windows的一个可选包,Internet Information Services (IIS)管理器经常被用于Windows Server系列服务器内的Web管理。IIS采用应用程序池方式管理Web的工作进程,同时采用了页面输出缓存的缓存加载机制。当网络出现瞬间访问异常时,部分IIS管理的web页面可能会发生长…

vue登录 滑动验证,记住密码及AES加密解密

相关依赖 npm install js-cookie //js-cookie npm install crypto-js //AES加密解密 npm install -S vue-puzzle-vcode //滑动验证 vue2 <template><div class"login"><div class"login-box"><div class"imgbox">&…

BOM介绍

文章目录 1、简介主要作用 2、BOM的组成2.1 窗口对象window2.1.1 window对象特点2.1.2 window作用域2.1.3 window对象常见方法**第一类&#xff1a;系统对话框**第二类&#xff1a;控制浏览器窗口方法第三类&#xff1a;与定时器有关的方法 1、简介 BOM&#xff08;Browser Ob…

面试算法98:路径的数目

题目 一个机器人从mn的格子的左上角出发&#xff0c;它每步要么向下要么向右&#xff0c;直到抵达格子的右下角。请计算机器人从左上角到达右下角的路径的数目。例如&#xff0c;如果格子的大小是33&#xff0c;那么机器人从左上角到达右下角有6条符合条件的不同路径。 分析…

教学/直播/会议触摸一体机定制_基于展锐T820安卓核心板方案

触控一体机是一种集先进的触摸屏、工控和计算机技术于一体的设备。它取代了传统的键盘鼠标输入功能&#xff0c;广泛应用于教学、培训、工业、会议、直播、高新科技展示等领域。触摸一体机的应用提升了教学、会议和展示的互动性和信息交流。 触摸一体机方案基于国产6nm旗舰芯片…

鸿蒙问题之本地模拟器无法识别

今天按例打开本地模拟器&#xff0c;发现DevEco Studio不能检测到我的本地模拟器了。 重启了DevEco Studio和模拟器多次都无果。果断删除模拟器 然后创建一个新的&#xff0c;就可以成功检测到了。这应该是idea的一个bug

外包干了5个月,技术明显退步了...

先说一下自己的情况&#xff0c;本科生&#xff0c;19年通过校招进入湖南某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年12月份&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测…