JUC 之 Synchronized 与 锁升级

news2024/11/27 2:20:58

—— 对象内存布局 和 对象头

对象构成布局

1. 对象头

对象标记 Mark Word

  • 哈希码
  • GC 标记 & 次数
    • GC 年龄 采用 4 位 bit 存储,最大为 15(1111),所以 MaxTenutingThreshold 参数(分代年龄)的参数默认值为 15
  • 同步锁标记
  • 偏向锁持有者
  • 默认存储对象的 HashCode,分代年龄和锁标志位等信息;这些信息都是与对象自身定义无关的数据,所以 MarkWord 被设置成一个固定的数据结构,以便在极小的空间内存储尽量多的数据。它会根据对象的状态复用自己的存储空间,也就是在运行期间 MarkWord 里存储的数据会随着锁标志位的变化而变化

类元信息(类型指针)

  • 对象指向它的类元数据的指针(存在方法的对象模板),虚拟机通过这个指针来确定这个对象是哪个类的实例

在64位系统中,Mark Word 占了 8 个字节,类型指针占了 8 个字节,一共是 16 个字节(忽略压缩指针的影响);相当于 new 一个空对象

2. 实例数据

  • 存放类的属性(Field) 数据信息,包括父类的属性信息

3.对齐填充

  • 虚拟机要求对象起始地址必须是8字节的整数倍。填充数据不是必须存在,仅仅是为了字节对齐这部分内存按8字节补充对齐
  • 为什么是 8 字节的整数倍?
    • 方便 JVM 计数,计算机大都是 64 位处理器,能处理 8 个字节的数据,性能更高,处理更快

在这里插入图片描述

—— Synchronized 与 锁升级

阿里规约: 高并发时,同步调用应该去考量锁的性能耗损。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁
锁的升级过程: 无锁 》 偏向锁 》轻量级锁 》 重量级锁

前言

  • Java 5 以前,只有 Synchronized,这是操作系统级别的重量级操作(需要用户态和内核态之间的切换
    在这里插入图片描述
  • 为什么每个对象都可以成为一个锁?
    • Java 对象是天生的 Monitor,每一个 Java 对象都有成为 Monitor 的潜质,因为在 Java 的设计中,每一个 Java 对象都自带一把内部锁 或者 Monitor 锁
    • Monitor 的本质是依赖底层操作系统 Mutex Lock 实现,操作系统实现线程之间的切换需要从用户态到内核态的转换,成本极高
  • Java 6 之后,为了减少获得锁和释放锁所带来的性能消耗,引入了轻量级锁 和 偏向锁

锁升级流程

synchronized 用的锁是存在 Java 对象头里的 Mark Word 中,锁升级功能主要依赖 Mark Word 中锁标志位 和 释放偏向锁标志位

锁指向

  • 偏向锁: Mark Word 存储的是偏向的线程ID
  • 轻量锁:Mark Word 存储的是指向线程栈中 Lock Record 的指针
  • 重量锁:Mark Word 存储的是指向堆中的 Monitor 对象的指针

偏向锁

  • 概念:单线程竞争,当线程 A 第一次竞争到锁时,通过操作修改 Mark Word 中 的偏向线程 ID 、偏向模式。如果不存在其他线程竞争,那么持有偏向锁的线程将永远不需要进行同步
  • 作用:当一段同步代码一直被同一个线程多次访问,由于只有一个线程,那么该线程在后续访问时便会自动获取锁
  • 大多数情况下,多线程中的锁不仅不存在多线程竞争,还存在锁由同一个线程多次获得的情况,偏向锁就是在这种情况下出现的,它的出现是为了解决只有在一个线程执行同步时提高性能
  • 偏向锁会偏向于第一个访问锁的线程,如果在接下来的运行过程中,该锁没有被其他的线程访问,则持有偏向锁的线程将永远不需要触发同步。也即偏向锁在资源没有竞争情况下消除了同步语句,连 CAS 操作都不做,直接提高程序性能
    在这里插入图片描述在这里插入图片描述
  • 参数说明:实际上偏向锁在 JDK1.6 之后是默认开启的,但是启动时间有延迟,所以需要添加参数-XX:BiasedLockingStartupDelay=0(关闭延迟),让其在程序启动时立刻启动
  • 开启偏向锁:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
  • 关闭偏向锁:-XX:-UseBiasedLocking(会跳级进入轻量锁)
  • 偏向锁撤销:
    • 当有另外线程逐步来竞争锁的时候,就不能再使用偏向锁了,要升级为轻量级锁
    • 竞争线程尝试 CAS 更新对象头失败,会等待到全局安全点 (此时不会执行任何代码)撤销偏向锁
  • 注意:Java 15 逐步废弃偏向锁,HotSpot 资源优化,开销较大

轻量级锁

  • 定义:多线程竞争,但是任意时刻最多只有一个线程竞争;即不存在锁竞争太过激烈的情况,也就没有线程阻塞
  • 主要作用: 有线程来参加 竞争,但是获取锁的冲突时间极短;本质就是自旋锁 CAS
  • 获取: 轻量级锁是为了在线程近乎交替执行同步块时提高性能,在没有多线程竞争的前提下,通过 CAS 中减少重量级锁使用操作系统互斥量产生的性能消耗,先自旋,不行才升级阻塞;当关闭偏向锁功能或多线程竞争偏向锁会导致偏向锁升级为轻量级锁
    在这里插入图片描述
  • CAS 自旋到达一定程度和次数之后会升级到重量级锁(Java 6 之后)
    • 自适应自旋锁的大致原理:线程如果自旋成功了,那下次自旋的最大次数会增加,因为 JVM 认为既然上次成功了,那么这一次也很大概率会成功;反之,如果很少会自旋成功,那么下次会减少自旋的次数甚至不自旋,避免 CPU 空转
    • 自适应意味着自旋的次数不是固定不变的,而是根据同一个锁上一次自旋的时间 或者 拥有锁线程的状态来决定
  • 偏向锁 和 轻量锁 的区别和不同:
    • 争夺轻量级锁失败时,自旋尝试抢占锁
    • 轻量级锁每次退出同步块都需要释放锁,而偏向锁是在竞争发生时才释放锁

重量级锁

  • 有大量的线程参与锁的竞争,冲突性很高
  • 原理: Java 中 synchronized 的重量级锁,是基于进入和退出 Monitor 对象实现的。在编译时会将同步块的开始位置插入 Monitor Enter 指令,在结束位置插入 Monitor Exit 指令。当线程执行到 Monitor Enter 指令时,会尝试获取对象所对应的 Monitor 所有权,如果获取到了,会在 Monitor 的 owner 中存放当前线程的 ID,这样它将处于锁定状态,除非退出同步块,否则其他线程无法获取到这个 Monitor

锁升级后,hashcode 去哪了?

  • 锁升级为轻量或 重量级锁后,Mark Word 中保存的分别是 线程栈帧里的锁记录指针重量级锁指针,已经没有位置再保存 hashcode、GC 年龄
  • 官方文档:一个对象如果计算过哈希码,就应该一致保持该值不变,否则很多依赖对象哈希码的API 可能存在出错风险。当一个对象以净计算过一致性哈希码后,它再也无法进入偏向锁状态了;而当一个对象当前正处于偏向锁的状态,又收到需要计算一致性哈希请求时,它的偏向状态会被立即撤销,并且锁会膨胀为重量级锁。在重量级锁的实现中,对象头指向了重量级锁的位置,代表重量级锁的 Monitor 类里有字段可以记录非加锁状态下的 Mark Word ,其中自然可以存储原来的哈希码
    在这里插入图片描述
    各个锁的优缺点
    在这里插入图片描述
  • 偏向锁: 适用于单线程,在不存在锁竞争的时候进入同步方法则使用偏向锁
  • 轻量级锁: 使用于竞争较不激烈的情况,存在竞争时升级为轻量级锁,轻量级锁采用的是自旋锁,如果同步方法执行时间很短的话,采用轻量级锁虽然会占用占用 CPU 资源但是相对比使用重量级锁还是更高效
  • 重量级锁:适用于竞争激烈的情况,如果同步方法执行时间很长,那么使用轻量级锁自旋带来的性能消耗就比使用重量级锁更严重,这时候就需要升级为重量级锁

JIT 编译器对锁的优化

  • JIT: Just In Time Complier,即时编译器
  • 锁消除:JVM 使用 JIT 对锁进行优化, 基于逃逸分析,如果局部变量在运行过程中没有出现逃逸,监测到了共享数据没有竞争的锁,可将这些锁进行消除
  • 锁粗化:将同步块的作用范围尽可能小,为了需要同步操作数量尽可能少,将多次上锁解锁的请求合并为一次同步请求

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

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

相关文章

C++回顾(三)—— 函数

3.1 内联函数 3.1.1 内联函数的定义 (1)内联函数的作用 作用:不是在调用时发生控制转移,而是在编译时将函数体嵌入在每一个调用处,适用于功能简单,规模较小又使用频繁的函数。递归函数无法内联处理&…

Java-重排序,happens-before 和 as-if-serial 语义

目录1. 如何解决重排序带来的问题2. happens-before1. 如何解决重排序带来的问题 对于编译器,JMM 的编译器重排序规则会禁止特定类型的编译器重排序。对于处理器重排序,JMM 的处理器重排序规则会要求编译器在生成指令序列时,插入特定类型的内…

Android笔记(二十五):两种sdk热更插件资源加载方案

背景 在研究sdk插件化热更新方式的过程中总结出了两套插件资源加载方案,在此记录下 资源热更方式 方式一:合并所有插件资源 需要解决资源id冲突问题 资源ID值一共4个字段,由三部分组成:PackageIdTypeIdEntryId PackageId&…

Mysql 事务的隔离性(隔离级别)

Mysql 中的事务分为手动提交和自动提交,默认是自动提交,所以我们在Mysql每输入一条语句,其实就会被封装成一个事务提交给Mysql服务端。 手动提交需要先输入begin,表示要开始处理事务,然后就是常见的sql语句操作了&…

C++之入门之命名空间、缺省参数、函数重载

一、前言 我们知道c是对c语言的完善以及再发展,所以C中的很多东西是与C语言十分修饰的,并且C也是兼容C的,学习了C之后,相信学C也不在困难,对我们来说,唯一感到不解和陌生就只有 using namespace std; 这条…

【c++】STL1—STL初识

文章目录STL的基本概念STL六大组件STL中容器、算法、迭代器容器算法迭代器容器算法迭代器初识vector存放内置数据类型vector存放自定义数据类型容器嵌套容器c的面向对象和泛型编程思想,目的就是复用性的提升。 为了建立数据结构和算法的一套标准,诞生了S…

并查集(13张图解)--擒贼先擒王

目录 前言 故事 🌼思路 🌼总结 🌼代码 👊观察过程代码 👊正确代码 👊细节代码 来自《啊哈算法》 前言 刚学了树在优先队列中的应用--堆的实现 那么树还有哪些神奇的用法呢?我们从一…

前端卷算法系列(二)

前端卷算法系列(二) 回文数 给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。 回文数是指正序(从左向右)和倒序(从右向左)读都是一样…

zookeeper集群的搭建,菜鸟升级大神必看

一、下载安装zookeeperhttp://archive.apache.org/dist/zookeeper/下载最新版本2.8.1http://archive.apache.org/dist/zookeeper/zookeeper-3.8.1/二、上传安装包到服务器上并且解压,重命名tar -zxvf apache-zookeeper-3.8.1-bin.tar.gzmv apache-zookeeper-3.8.1-b…

设计环形队列

文章目录1.思路分析1.1队列空满分析1.2出队分析2.循环队列设计1.思路分析 1.1队列空满分析 首先我们假设一个长度为4的环形队列 队头front 队尾rear 当队列为空时 frontrear 当队列满时 frontrear 所以我们无法判断队列是满的或者空的 因此我们多加入一个空间使队列长度为5&am…

什么是自适应平台服务?

总目录链接==>> AutoSAR入门和实战系列总目录 文章目录 什么是自适应平台服务?1.1 自适应平台服务包含哪些功能簇呢?1.1.1 ara::sm 状态管理 (SM)1.1.2 ara::diag 诊断管理 (DM)1.1.3 ara::s2s 信号到服务映射1.1.4 ara::nm 网络管理 (NM)1.1.5 ara::ucm 更新和配置管…

数据结构期末复习总结(前章)

作者的话 作为一名计算机类的学生,我深知数据结构的重要性。在期末复习前,我希望通过这篇博客给大家一些复习建议。希望能帮助大家夯实数据结构的基础知识,并能够更好地掌握数据结构和算法的应用。 一、绪论 数据:信息的载体&am…

【测试】loadrunner安装

努力经营当下,直至未来明朗! 文章目录备注一、下载安装包二、安装loadrunner三、修改浏览器配置今天搬砖不努力,明天地位不稳定! 备注 电脑最好有IE浏览器,但是没有也没事儿。(注意:IE浏览器不…

Bootstrap系列之栅格系统

Bootstrap栅格系统 bootatrap提供了一套响应式,移动设备优先的流式网格系统,随着屏幕或者视口尺寸的增加,系统会自动分为最多12列,多出12列的将不再此行显示(换行显示) bootstrap网格系统有以下六个类 重点…

华为OD机试用Python实现 -【云短信平台优惠活动】(2023-Q1 新题)

华为OD机试题 华为OD机试300题大纲云短信平台优惠活动题目描述输入描述输出描述示例一输入输出说明示例二输入输出说明Python 代码实现代码编写思路华为OD机试300题大纲 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 华为 OD 清单查看…

【Java基础】操作系统原理

一、进程 进程是指一段程序的执行过程,会消耗系统资源如CPU、内存、网络等。 一个进程包含静态代码段,数据,寄存器地址等 进程的特点 动态性(可动态地创建、结束进程) 并发性(进程被独立调度并占用处理…

服务器部署—若依【vue】如何部署到nginx里面?nginx刷新页面404怎么办?【完美解决建议收藏】

服务器部署项目我们大家都会遇到,但是有些铁子会遇到很多的问题,比如前端部署nginx如何操作? 前端有单纯的静态页面、还有前后端分离的项目;这里博主直接分享最牛最到位的前后端分离项目的前端部署到nginx上面,以若依项…

C语言之习题练习集

💗 💗 博客:小怡同学 💗 💗 个人简介:编程小萌新 💗 💗 如果博客对大家有用的话,请点赞关注再收藏 🌞 文章目录牛客网题号: JZ17 打印从1到最大的n位数牛客网题号&#x…

Laravel框架03:DB类操作数据库

Laravel框架03:DB类操作数据库一、概述二、数据表的创建与配置三、增删改操作1. 增加信息2. 修改数据3. 删除数据四、查询操作1. 取出基本数据2. 取出单行数据3. 获取一个字段的值4. 获取多个字段的值5. 排序6. 分页五、执行任意的SQL语句一、概述 按照MVC的架构&a…

详讲函数知识

目录 1. 函数是什么? 2. C语言中函数的分类: 2.1 库函数: 2.2 自定义函数 函数的基本组成: 3. 函数的参数 3.1 实际参数(实参): 3.2 形式参数(形参): …