【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.mzph.cn/news/599763.shtml

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

相关文章

互联网广告行业发展历程

在20年的历程中&#xff0c;广告主与媒体方持续面对着一些问题&#xff0c;一些核心问题推动了行业的迭代。 互联网广告经过了20年左右的高速发展&#xff0c;已愈发成熟&#xff0c;其历程是有趣的。 对互联网广告发展的理解&#xff0c;网上的文章并不多&#xff0c;已有的…

第12课 利用openCV检测物体是否运动了

FFmpeg与openCV绝对是绝配。前面我们已经基本熟悉了FFmpeg的工作流程&#xff0c;这一章我们重点来看看openCV。 在前面&#xff0c;我们已经使用openCV打开过摄像头并在MFC中显示图像&#xff0c;但openCV能做的要远超你的想像&#xff0c;比如可以用它来实现人脸检测、车牌识…

【Netapp数据恢复】Netapp存储lun被删除如何恢复数据?

Netapp存储数据恢复环境&故障情况&#xff1a; 某单位一台Netapp存储&#xff0c;该Netapp存储内共有数十块SAS硬盘。 工作人员误操作删除了Netapp存储中12个lun&#xff0c;删除的数据包括客户信息和其他重要数据。 Netapp存储数据恢复过程&#xff1a; 1、将故障存储中所…

C#利用openvino部署PP-TinyPose人体姿态识别

【官方框架地址】 github.com/PaddlePaddle/PaddleDetection 【算法介绍】 关键点检测算法往往需要部署在轻量化、边缘端设备上&#xff0c;因此长期以来都存在一个难题&#xff1a;精度高、速度则慢、算法体积也随之增加。而PP-TinyPose的出世彻底打破了这个僵局&#xff0c…

43 tmpfs/devtmpfs 文件系统

前言 在 linux 中常见的文件系统 有很多, 如下 基于磁盘的文件系统, ext2, ext3, ext4, xfs, btrfs, jfs, ntfs 内存文件系统, procfs, sysfs, tmpfs, squashfs, debugfs 闪存文件系统, ubifs, jffs2, yaffs 文件系统这一套体系在 linux 有一层 vfs 抽象, 用户程序不用…

Selenium库和ChromeDriver谷歌驱动最新版安装

1.安装selenium库 使用pip安装第三方库selenium&#xff0c;速度较慢。 pip install selenium 使用国内清华源安装第三方库selenium&#xff0c;速度较快。 pip install selenium -i https://pypi.tuna.tsinghua.edu.cn/simple 2.安装谷歌浏览器驱动 驱动下载链接&#x…

【生成人工智能】Ray如何解决生成人工智能基础设施的常见生产挑战

这是我们生成人工智能博客系列的第一部分。在这篇文章中&#xff0c;我们讨论了如何使用Ray来生产常见的生成模型工作负载。即将发布的一篇博客将深入探讨Alpa等项目为什么要使用Ray来扩展大型模型。 生成的图像和语言模型有望改变企业的设计、支持、开发等方式。本博客重点关…

Android ValueAnimator属性动画ObjectAnimator使View颜色渐变,Kotlin

Android ValueAnimator属性动画ObjectAnimator使View颜色渐变&#xff0c;Kotlin 设置背景颜色渐变&#xff1a; private var iv: ImageView? nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activit…

如何使用 Python、Node.js 和 Go 创建基于 YOLOv8 的对象检测 Web 服务

1. 介绍 这是有关 YOLOv8 系列文章的第二篇。在上一篇文章中我们介绍了YOLOv8以及如何使用它&#xff0c;然后展示了如何使用 Python 和基于 PyTorch 的官方 YOLOv8 库创建一个 Web 服务来检测图像上的对象。 在本文中&#xff0c;将展示如何在不需要PyTorch和官方API的情况下…

IDEA 2023快捷键

1、main | sout | psvm 2、CTALD 复制当前行 3、ALT SHIFT ↕ 可以把当前行代码进行移动 4、CTRLH 类的继承继承结构 5、CTRLF12 类的成员 6、 SHIFTF6 统一修改变量 7、CTRLATLH 方法调用层级 8、ALT1 是否展示左侧菜单 9、ALTinsert 生成一些代码 10、CTRLP 提示参数…

【React系列】Redux(二)中间件

本文来自#React系列教程&#xff1a;https://mp.weixin.qq.com/mp/appmsgalbum?__bizMzg5MDAzNzkwNA&actiongetalbum&album_id1566025152667107329) 一. 中间件的使用 1.1. 组件中异步请求 在之前简单的案例中&#xff0c;redux中保存的counter是一个本地定义的数据…

给新手的25个建议

前言 最近知乎上&#xff0c;有一位大佬邀请我回答下面这个问题&#xff0c;看到这个问题我百感交集&#xff0c;感触颇多。 在我是新人时&#xff0c;如果有前辈能够指导方向一下&#xff0c;分享一些踩坑经历&#xff0c;或许会让我少走很多弯路&#xff0c;节省更多的学习的…

2024最新阿里云服务器地域(城市)对照表

2024年阿里云服务器地域分布表&#xff0c;地域指数据中心所在的地理区域&#xff0c;通常按照数据中心所在的城市划分&#xff0c;例如华北2&#xff08;北京&#xff09;地域表示数据中心所在的城市是北京。阿里云地域分为四部分即中国、亚太其他国家、欧洲与美洲和中东&…

Nginx 的 gzip 压缩

目录 1. 为什么要开启gzip 压缩 2.对网站配置文件进行修改 1. 为什么要开启gzip 压缩 nginx使用gzip压缩主要是为了降低网站的带宽消耗和提升访问速度。通过对页面进行压缩&#xff0c;可以减少传输的数据量&#xff0c;从而减少网络传输的时间和带宽消耗。 当浏览器接收到压…

Java常用API

一、实体JavaBean 接下来&#xff0c;我们学习一个面向对象编程中&#xff0c;经常写的一种类——叫实体JavaBean类。我们先来看什么是实体类&#xff1f; 1. 什么是实体类&#xff1f; 实体类就是一种特殊的类&#xff0c;它需要满足下面的要求&#xff1a; 接下来我们按照…

burpsuite 爆破

靶场搭建:phpstudy的安装与靶场搭建 - junlin623 - 博客园 (cnblogs.com) 账号字典:XXTK: 一些弱口令、fuzz字典 (gitee.com) 网盘链接:https://pan.baidu.com/s/1v5pAwaTwoeCnJgkUXf3iLQ?pwd=mllm 提取码:mllm --来自百度网盘超级会员V2的分享 一、暴力破解 - 基于…

将Django项目从本地上传至宝塔服务器(踩坑记录)

文章目录 写在前面配置本地文件配置宝塔面板解决遇到问题展示运行结果热门文章 自我介绍 ⭐2022年度CSDN 社区之星 Top6 ⭐2023年度CSDN 博客之星 Top16 ⭐2023年度CSDN 城市之星 Top2&#xff08;苏州&#xff09; ⭐CSDN Python领域 优质创作者 ⭐CSDN 内容合伙人 推荐热门…

jmeter下载及安装配置

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;软件测试面试题分享&#xff1a; 1000道软件测试面试题及答案&#x1f4e2;软件测试实战项目分享&#xff1a; 纯接口项目-完…

lvs+keepalived+nginx实现四层负载+七层负载

目录 一、lvs配置 二、nginx配置 三、测试 3.1 keepalived负载均衡 3.2 lvskeepalived高可用 3.3 nginx高可用 主机IPlvs01-33 11.0.1.33 lvs02-3411.0.1.34nginx0111.0.1.31nginx0211.0.1.32VIP11.0.1.30 4台主机主机添加host [rootnginx01 sbin]# cat /etc/hosts 127.0.0.…

【力扣每日一题】力扣2478从链表中移除节点

题目来源 2478.从链表中移除节点 题目描述 给你一个链表的头节点 head 。 移除每个右侧有一个更大数值的节点。 返回修改后链表的头节点 head 。 示例 示例1&#xff1a; 输入&#xff1a;head [5,2,13,3,8] 输出&#xff1a;[13,8] 解释&#xff1a;需要移除的节点是 5 …