【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                               VALUE0     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加解锁,粗化成一次。
在这里插入图片描述

总结

在这里插入图片描述

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

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

相关文章

【Sublime Text】| 02——常用插件安装及配置

系列文章目录 【Sublime Text】| 01——下载软件安装并注册 【Sublime Text】| 02——常用插件安装及配置 失败了也挺可爱&#xff0c;成功了就超帅。 文章目录 1. 汉化2. 更换颜色主题3. 更改编码插件—ConvertToUTF84. 对齐插件—Alignment5. 括号高亮插件—BracketHighligh…

win11修改本地hosts,自定义域名

目录 &#x1f9c8;1.打开指定目录 &#x1f95a;2.粘贴至桌面 &#x1f373;3.添加自己的域名和对应的ip地址 &#x1f37f;4.替换原来的hosts文件 1.打开指定目录&#x1f9c2;&#x1f9c2; 在C盘下打开 --------C:\Windows\System32\drivers\etc&#xff0c;找到hos…

众和策略:沪指跌0.91%险守2900点,半导体、金融等板块走低

8日早盘&#xff0c;两市股指低开低走&#xff0c;沪指一度失守2900点&#xff0c;深成指、创业板指跌约1%&#xff0c;科创50指数创前史新低。 到午间收盘&#xff0c;沪指跌0.91%报2902.4点&#xff0c;深成指跌1.17%&#xff0c;创业板指跌0.99%&#xff0c;科创50指数跌超…

vue3中使用elementplus中的el-tree-select,自定义显示名称label

<el-tree-select v-model"addPval" node-key"id" :data"menulists" :render-after-expand"false" :props"menuProps" /> <el-divider />let menuProps {//自定义labellabel: (data: { name: any; }) > {ret…

程序媛的mac修炼手册-- 终端(terminal)常用命令

「终端&#xff08;terminal&#xff09;」相当于macOS的一个 App &#xff0c;它的特殊之处是&#xff0c;它是管理其它App的App&#xff0c;操作主要通过命令行界面 (CLI) 。 相比于我们日常熟悉的用户界面&#xff08;User Interface&#xff0c;UI&#xff09;&#xff0c…

vue3 封裝一个常用固定按钮组件(添加、上传、下载、删除)

效果图 这个组件只有四个按钮&#xff0c;添加&#xff0c;上传、下载、删除&#xff0c;其中删除按钮的颜色默认是灰色&#xff0c;当表格有数据选中时再变成红色 实现 组件代码 <script lang"ts" setup> import { Icon } from /components/Icon/index im…

Qt应用-实现图像截取功能类似QQ上传头像截取功能

本文演示利用Qt实现图像截取功能类似QQ上传头像截取功能。 效果如下,通过移动中间的裁剪区域可以获得一张裁剪后的图片。 目录

OpenAI ChatGPT-4开发笔记2024-02:Chat之text generation之completions

API而已 大模型封装在库里&#xff0c;库放在服务器上&#xff0c;服务器放在微软的云上。我们能做的&#xff0c;仅仅是通过API这个小小的缝隙&#xff0c;窥探ai的奥妙。从程序员的角度而言&#xff0c;水平的高低&#xff0c;就体现在对openai的这几个api的理解程度上。 申…

【unity】基于Obi的绳长动态修改(ObiRopeCursor)

文章目录 一、在运行时改变绳子长度:ObiRopeCursor1.1 Cursor Mu&#xff08;光标μ&#xff09;1.2 Source Mu&#xff08;源μ&#xff09;1.3 Direction&#xff08;方向&#xff09; 一、在运行时改变绳子长度:ObiRopeCursor Obi提供了一个非常通用的组件来在运行时修改绳…

Vue3-39-路由-导航异常的检测 afterEatch 与 编程式导航之后的订阅动作

说明 本文主要是介绍一下 路由的后置守卫 afterEatch 的一个重要的作用 &#xff1a; 就是检测路由异常信息。 它的实现方式是 通过第三个参数来返回的。 而且&#xff0c;它的异常检测是全局的。导航的异常有以下三种类型&#xff1a; aborted : 在导航守卫中 被拦截并返回了…

苹果Find My查找芯片-伦茨科技ST17H6x支持苹果Find My认证

Apple「查找」Find My可通过庞大的“Apple Find My Network” 实现全球查找功能。无数iOS、iPadOS、macOS、watchOS激活设备与Find My 设备结合在一起&#xff0c;无需连接到Wi-Fi或者蜂窝网络&#xff0c;用户也可以给遗失的设备定位。对于任何iOS、iPadOS、macOS、watchOS设备…

Plantuml之nwdiag网络图语法介绍(二十九)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

算法第十一天-组合总和Ⅳ

组合总和Ⅳ 题目要求 解题思路 来自[负雪明烛] 题目有个明显的提示&#xff1a;求组合的个数&#xff0c;而不是每个组合。如果是要求出每个组合&#xff0c;那么必须使用回溯法&#xff0c;保存所有路径。但是如果是组合个数&#xff0c;一般都应该想到[动态规划]的解法。 直…

*4.3 CUDA MEMORY TYPES

CUDA设备包含几种类型的内存&#xff0c;可以帮助程序员提高计算到全局内存的访问率&#xff0c;从而实现高执行速度。图4.6显示了这些CUDA设备内存。全局内存和恒定内存出现在图片的底部。主机可以通过调用API函数来写入&#xff08;W&#xff09;和读取&#xff08;R&#xf…

Python私有变量的定义与访问

class Student():def __init__(self, name, age):self.name nameself.age ageself.__score 0def marking(self, score):if score < 0:return 分数不能为0self.__score scoreprint(self.name 同学本次得分是: str(self.__score)) def __talk(self): # 私有的类可通过在…

Python爬虫-爬取豆瓣Top250电影信息

&#x1f388; 博主&#xff1a;一只程序猿子 &#x1f388; 博客主页&#xff1a;一只程序猿子 博客主页 &#x1f388; 个人介绍&#xff1a;爱好(bushi)编程&#xff01; &#x1f388; 创作不易&#xff1a;喜欢的话麻烦您点个&#x1f44d;和⭐&#xff01; &#x1f388;…

Wargames与bash知识12

Wargames与bash知识12 Bandit20 关卡提示&#xff1a; 主目录中有一个setuid二进制文件&#xff0c;它执行以下操作&#xff1a;它在您指定为命令行参数的端口上连接到localhost。然后&#xff0c;它从连接中读取一行文本&#xff0c;并将其与前一级别的密码&#xff08;band…

java: 5-4 while循环

文章目录 1. while循环1.1 基本语法1.2 流程图![请添加图片描述](https://img-blog.csdnimg.cn/direct/902ee10622a74b689f18eff6b4a2a61e.png)1.3 练习1.4 细节1.5 练习题 1. while循环 1.1 基本语法 1.2 流程图 1.3 练习 输出 10 句 你好,韩顺平教育。 public class var0…

使用 dbgate 在 sealos 上完美管理 mysql pgsql 等数据库

先登录 sealos 创建数据库&#xff0c;可以创建个 pgsql: 再到模版市场启动 dbgate: 配置数据库的连接信息&#xff0c;即可搞定收工 sealos 以kubernetes为内核的云操作系统发行版&#xff0c;让云原生简单普及 laf 写代码像写博客一样简单&#xff0c;什么docker kubernete…

echarts - xAxis.type设置time时该如何使用formatter的分级模板

echarts 文档中描述了x轴的多种类型 一、type: ‘value’ ‘value’ 数值轴&#xff0c;适用于连续数据。 此时x轴数据是从零开始&#xff0c;有数据大小的区分。 【注意】 因为xAxis.data是为category服务的&#xff0c;所以xAxis.data里面设置的数据无效。 二、type: ‘ca…