多线程Synchronized关键字-------你所不了解的锁升级过程(内附详细原理图)

Synchronized实现原理与锁升级机制 并发编程,要求线程之间能够相互配合以完成工作。这就涉及到数据共享和线程协作。 Java支持多个线程公式访问同一个对象的方法和成员变量,而关键字synchronized的作用则是确保多线程在同一时刻,只能有一个线程访问synchronized所修饰的方法或同步块,确保线程访问的排他性。 synchronized是内置在对象头的MarkWork字段。对象头的构成在我的"Jvm对象创建过程"博客中有详细说明。因此,它又被称为内置锁。 对象锁和类锁... 阅读详情

本文主要介绍Sync锁的升级过程以及介绍锁信息,其中的CAS,MarkWord,偏向锁,轻量级锁…等知识点的讲解,不讲解锁的其他优化,其他锁优化会在下一篇进行讲解。

1.什么是Synchronized关键字?

synchronized关键字用来解决多线程之间访问资源的同步问题,它可以保证被它修饰的方法或者代码块在任何时刻只有一个线程能够执行。
在 Java 早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock(互斥锁) 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要比较长的时间,时间成本较高,所以早期的 synchronized 效率比较低。
但是在 Jdk1. 6 之后Java 官方从 JVM 层面对synchronized 做了较大的优化,引入自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。

2.Sync锁升级过程?

锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。这里的锁只能升级而不能降级,这种策略也是为了提高获得锁和释放锁的效率。

2.1 什么是MarkWord?

我们都知道对象通常由三部分组成,对齐填充字节,实例数据,以及对象头。而MarkWord就是对象头的一部分。

对象头:(64位虚拟机)
    1.根据对象类型来划分内存大小,如果对象是数组类型,则虚拟机用160bit也就是20个字节。来存储对象头,其中64bit存放MarkWord + 64bit存放类元数据的指针 + 32bit存放数组的长度。
    2.如果对象是非数组类型,则用128bit,也就是16个字节,来存储对象头。其中64bit存放MarkWord + 64bit存放类元数据的指针。
        1.MarkWord:
    主要就是存放对象的分代年龄,HashCode,锁的信息。
        2.类元数据的指针:
    虚拟机通过这个指针来确定该对象是属于哪个类的,而类的元信息就在方法区中,所以是指向方法区的,当调用该对象的方法的时候,就是根据对象头的这个指针,来调用到方法。

就是说对象头分为数组类型和非数组类型,主要区别就只是多了一个数组长度的空间。

MarkWord内存图:
在这里插入图片描述

2.2 偏向锁

偏向锁的“偏”就是偏心的偏,它的意思就是会偏向于第一个获得它的线程,如果在接下来的执行中,该锁没有被其他线程获取,那么持有偏向锁的线程就不需要进行同步,偏向锁会就把整个同步都消除掉。
偏向锁的目的其实就是,想要在没有竞争且只有一个线程使用锁的情况下,减少轻量级锁所带来的性能消耗。因为轻量级锁每次获取、释放锁都至少需要执行一次CAS原子操作,而偏向锁只有初始化时需要执行一次CAS操作。所以在没有线程竞争的情况下,它的效率是较高的。
偏向锁加锁原理:
当JVM启用了偏向锁模式,新创建对象的Mark Word中的Thread Id为0,说明此时处于可偏向但未偏向任何线程的状态,也叫做匿名偏向状态(anonymously biased)。

  1. 第一个线程来访问同步块时,先检测Mark Word中的锁标志位是否为01,是的话说明是偏向锁,再判断偏向锁标志位是否为1,如果不是,则是其他锁状态。然后如果锁标志位为0,则走轻量级锁逻辑。如果锁标志位为1,则检查Mark Word中的Thread Id是否是当前线程ID。
  2. 如果是,则表明当前线程已经获得对象锁,接着执行同步代码。以后该线程进入同步块时,都不需要进行CAS操作,只会往当前线程的栈中添加一条锁记录,用来统计重入的次数。
  3. 如果对象头Mark Word中Thread Id不是当前线程ID,则进行CAS操作,企图将当前线程ID替换进Mark Word中。如果当前对象锁状态处于匿名偏向锁状态,则会替换成功(将Mark Word中的Thread id由0改成当前线程ID,然后在当前线程的栈中将线程ID存入Lock Record),获取到锁,执行同步代码块;
  4. 如果对象锁已经被其他线程占用,则会替换失败,开始进行偏向锁撤销,这也是偏向锁的特点,一旦出现线程竞争,就会进行偏向锁撤销;

CAS是什么:CAS是一种无锁算法,CAS通常有3个操作数,内存值V,预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

偏向锁的CAS:
内存中的值:就是MarkWord中的线程id值,可能是当前持有偏向锁的线程id,或者为0表示未被线程占用。
预期值:假设对象头中的线程id为空,也就是为0。
要修改的新值:也就是将MarkWord中的线程id改为当前要获取锁的线程id。

偏向锁撤销原理:

  1. 偏向锁的撤销,是需要等待全局安全点的(这个时间点上没有正在执行的工作线程,下面有解释)。首先它会暂停拥有偏向锁的线程,然后检查持有偏向锁的线程状态。(遍历当前JVM的所有线程,如果能找到,则说明偏向的线程还存活)
  2. 如果线程还存活,则检查线程是否在执行同步代码,如果是,则升级为轻量级锁,当前持有偏向锁的线程获得锁,唤醒暂停的线程,从安全点继续执行代码,执行完代码再走轻量级锁的释放锁逻辑。
  3. 如果持有偏向锁的线程未存活,或者持有偏向锁的线程已经执行完同步代码块中的代码,则进行校验是否允许重偏向(判断epoch是否过期,epoch是一个时间戳),如果不允许重偏向,则撤销偏向锁,将Mark Word设置为无锁状态(无锁不可偏向的状态),然后升级为轻量级锁,进行CAS操作获得锁,就是竞争线程去持有轻量级锁。
  4. 如果允许重偏向,设置为匿名偏向锁状态,通过CAS操作将偏向锁指向新的线程(在对象头和线程栈帧的锁记录中存储当前线程ID)
  5. 唤醒安全点挂起的线程,继续执行代码。

全局安全点:
从线程的角度,安全点是代码执行中的一些特殊位置,当线程执行到这些特殊的位置,如果此时在GC,那么在这个地方线程会暂停,直到GC结束。
GC的时候要挂起所有活动的线程,因此线程挂起,会选择在到达安全点的时候挂起。
安全点这个特殊的位置保存了线程上下文的全部信息。在进入安全点的时候打印日志信息能看出线程此刻都在干嘛。
等待所有用户线程进入安全点后并阻塞,然后做一些全局性操作的行为,例如:GC行为,偏向锁撤销等。

2.3 轻量级锁:

在偏向锁失败后,虚拟机并不会立即将锁升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段。轻量级锁不是为了代替重量级锁,它的本意是在没有多线程竞争的前提下,减少使用重量级锁所产生的性能消耗,因为使用轻量级锁时候,不需要去申请互斥量。
轻量级锁之所以能够提升程序同步性能的依据是,“整个同步周期内都是不存在竞争的”。因为交替执行的线程获取锁,并不会发生竞争。如果没有竞争,轻量级锁会使用 CAS 操作来避免使用互斥操作产生的开销。但如果存在锁竞争,除了互斥量的开销外,还会有额外的CAS操作,因此在有锁竞争的情况下,轻量级锁会比传统的重量级锁要更慢!而且如果锁竞争的激烈,轻量级将会很快膨胀为重量级锁!

轻量级锁加锁原理:

  1. 如果当一个未获得锁的线程进入同步代码块的时候如果该对象是无锁状态,即标志位是00,或者锁升级为轻量级锁,那么就走轻量级锁的逻辑,首先它会在当前线程的栈帧中建立一个lock record 锁记录空间,将markword拷贝到锁记录中
  2. 然后执行CAS操作,操作成功则把markword更新为指向当前线程锁记录的指针,就表示该线程拥有了对象锁。
  3. 如果CAS操作失败了,会执行自旋锁逻辑,自旋锁失败达到一定次数后才升级为重量级锁,就会将MarkWord修改为重量级锁的信息。
    轻量级锁的CAS:
    预期值:是空值 或者 自己的锁记录指针
    要修改的新值:则是将指针指向自己的锁记录 或者 markword的原始信息
    内存中的值:锁当前真实状态,可能被占用,可能为空

轻量级锁释放锁原理:

  1. 判断对象头中的MarkWork中的锁记录指针是否仍然指向自己,是的话就会使用原子的CAS操作将锁记录中的Mark Word信息替换回到对象头。
  2. 如果成功,则表示没有竞争发生。
  3. 如果不是或者cas操作失败,直接释放掉这个锁,然后唤醒被挂起的线程(为什么不是呢,因为锁被升级为重量级锁,MarkWord被改变了,注意中有解释)

注意:当超过自旋的阈值,来竞争的线程就会把锁对象Mark Word指向重量级锁Monitor,从而导致Mark Word中的值发生了变化,当原持有轻量级锁的线程执行完毕,尝试通过CAS释放锁时,因为Mark Word已经指向重量级锁,不再是指向当前线程Lock Record的指针,于是解锁失败,这时原持有轻量级锁的线程就会知道锁已经升级为重量级锁,持有轻量级锁的线程就会直接释放锁,然后通知被挂起的线程。就是说新来的线程自旋失败了,将锁升级了,但是没拿到锁。被挂起了。

2.4 自旋锁:

轻量级锁失败后,虚拟机为了避免线程在操作系统的层面上被挂起,还会进行一项称为自旋锁的优化手段。
我们知道互斥同步对性能最大的影响就是阻塞的实现,因为挂起线程/恢复线程的操作都需要转入内核态中完成(用户态转换到内核态非常耗费时间)。
一般线程持有锁的时间都不会太长,所以为了这一点时间去挂起线程/恢复线程是得不偿失的。 所以我们可以让后面来请求锁的线程等待一会而不被挂起,只需要让线程去执行一个空循环(自旋)不断尝试获取锁,那么这个就叫做自旋锁。
自旋锁在 JDK1.6 之前其实就已经引入了,不过是默认关闭的。JDK1.6之后,就改为默认开启了。
注意:自旋等待不能完全替代阻塞,因为它还是要占用cpu的时间。如果锁被占用的时间短,倒没什么问题,但如果占用时间长,效果就很差,这样就会造成cpu资源浪费!所以自旋等待的时间必须要有一个限度。如果自旋的次数超过了指定的次数依然没有获得到锁,就会挂起线程。避免造成cpu资源的浪费。

最后附上一张锁升级过程的流程图:
在这里插入图片描述

深入解析Java中的synchronized关键字 接下来是偏向锁,我们还是用同样的代码即可,需要注意的是偏向锁必须在jvm启动后的一段时间才会运行,所以如果我们想打印偏向锁必须让线程休眠那么几秒,这里笔者就偷懒了一下,通过设置jvm参数。打印结果如下,我们只需关注第一行的object header,可以看到第一列的00000001,我们看到后3位为001,偏向锁标记为0,锁标记为01,001这就是我们说的无锁状态。上文讲解锁升级的之后,我们一直在说对象头的概念,所以为了能够直观的看到锁对象中对象头锁标记和锁状态的变化,我们这里引入一个jol工具。 阅读详情

相关推荐

synchronized 锁升级的原理

锁升级的目的是为了降低锁带来的性能消耗。偏向锁适用于只有一个线程访问共享资源的场景,轻量级锁适用于有少量竞争的场景,而重量级锁适用于高度竞争的场景。这种锁的升级策略有助于减小性能开销,因为大多数情况下只有少数线程会争夺锁,而大部分时间可以避免阻塞和上下文切换。

学无止境 624

synchronized锁升级过程

从理论到实操,详细介绍了锁的底层升级过程并用代码进行验证,加深记忆

在路上 1万+

【Java 多线程 1】java多线程并发编程

一、Synchronized保证原子性 public class Test { private static int number = 0; private static Object obj = new Object(); public static void main(String[] args) throws InterruptedException { Runnable increment = () -> { for (int

学Java,找哪吒 1万+

Synchronized的锁升级过程是怎样的?

关键字加到static静态方法和代码块上都是是给 Class 类上锁;关键字加到实例方法上是给对象实例上锁;尽量不要使用,因为 JVM 中,字符串常量池具有缓存功能,多个线程使用相同的字符串值,实际使用的是同一个对象。

weixin_45102820的博客 4523

(25.08)Ubuntu20.04+ROS1复现FAST-LIO2

请确保在。

m0_69023437的博客 3996

Ansys Lumerical 启动时出现 Error: Could not connect to Ansys license server specified at 1055@ 的另一种解决办法

昨天在关闭了中之后 就能正常打开 FDTD 了,结果今天想开启出现了同样的报错:我猜测昨天能启动的原因在于关闭之后要重启电脑,并且在重启之后我第一时间打开 lumerical 测试因此端口没有被占用,今天是在电脑启动之后一段时间才打开所以又遇到了端口占用的问题。

qq_51404184的博客 9560

关于Synchronized的偏向锁,轻量级锁,重量级锁,锁升级过程,自旋优化,你该了解这些

目录 前言 可见性问题及解决 概念描述 代码展示 分析 synchronized如何解决可见性 原子性问题及解决 概念描述 代码展示 分析 synchronized如何解决原子性问题 有序性问题及解决 概念描述 代码展示 synchronized如何解决有序性问题 synchronized的常见使用方式 修饰代码块(同步代码块) 修饰方法 synchroni...

路漫漫其修远兮,吾将上下而求索。 1596

Quartus原理图设计入门:从半加器到4位全加器的保姆级教程

本文提供Quartus原理图设计入门教程,详细讲解从半加器到4位全加器的实现过程。通过原理图输入法,逐步构建数字电路设计基础模块,涵盖环境搭建、逻辑设计、层次化实现及功能验证等关键步骤,帮助FPGA初学者快速掌握Quartus工具链和加法器设计方法。

weixin_29291185的博客 466

【并发编程】 --- synchronized锁的升级过程 + JDK1.6对synchronized关键字的其他优化简介

文章目录 源码地址:https://github.com/nieandsun/concurrent-study.git

nrsc 1104

OpenClaw 最强技能 self-improving-agent 详解:让 AI 从错误中自主学习

self-improving-agent 是 OpenClaw 生态中最受欢迎的技能插件,下载量突破 268k。它能让 AI 记住犯过的错误和解决方案,实现持续自我改进。本文深入讲解其工作原理、安装配置和实战案例。测试数据显示:部署流程时间从 15 分钟降到 2 分钟(87%↓),API 调试时间从 2 小时降到 20 分钟,数据库查询编写时间减少 90%。文章包含完整架构图、配置文件示例、3 个详细实战案例和性能测试数据,适合开发者、运维和数据分析师使用。

qq_38610923的博客 3636

新手必看:C#调用YOLO模型完成图片/视频目标检测(上位机版)

新手学习C#调用YOLO模型,核心是先把基础流程跑通,再逐步优化。图片检测是基础,视频检测就是在图片的基础上加循环和线程,而上位机的实际项目,就是在这个基础上适配工业硬件和业务需求。本文的代码都是新手能直接复制运行的,如果你运行时遇到问题,先检查前期准备是否到位,再看避坑指南。记住,新手别追求一步到位,先实现功能,再慢慢优化性能和界面。等你能熟练调用YOLO模型后,再去研究模型训练、量化优化等进阶内容,会轻松很多。

这里只有干货:从 Modbus 通信到 MES 对接,从 YOLO 训练到工控机部署,带你搞定工业软件开发全流程。 988

Synchronized锁及其原理(内有思维图)

Sychronized锁

是小D吖!的博客 751

13. MIC配置

恒玄BES2700,支持3个MIC。

雪飞_海 929

你真的了解Synchronized吗?深入JVM,图文并茂的理解Synchronized的加锁原理(锁的概念、同步关键字的概念、同步关键字的原理)

提纲 认识锁 锁的概念 悲观锁 概念:假定会发生并发冲突,同步所有对数据的相关操作,从读数据就开始上锁。 实例:Synchronized 就是悲观锁 乐观锁 概念:假定没有冲突,在修改数据时如果发现数据和之前获取的不一致,则读最新数据,修改后重试修改。 实例:CAS机制 自旋锁 概念:不放弃CPU事件,不断使用 CAS 尝试对数据进行更新,直到成功 实例:AtomicInt 使用自旋锁,保证数据的原子性 其实也是一种乐观锁 可重入锁 概念:同一个线程,在拿到一次锁之后,可以继续调用

留夕的博客 467

D16: OpenClaw + Notion API:知识管理新玩法

本文介绍了如何利用OpenClaw连接Notion API构建自动化知识管理系统。文章首先分析了传统知识管理的痛点,包括信息分散、更新不及时等问题,并对比了Notion与其他工具在API完整性、结构化程度等方面的优势。接着详细说明了Notion API的获取和授权流程,并设计了包含项目知识库、技术决策库和会议纪要库的三层架构系统。最后通过会议纪要自动归档的实战案例,展示了如何通过YAML配置实现从飞书会议到Notion的自动化知识沉淀流程。该方案能有效解决团队知识管理中的信息碎片化问题,提升协作效率。

刘一说的IT专栏 276

LabVIEW实现HTTP通信

本节简要介绍HTTP通信的基础知识,并以工业物联网平台TLINK中云端设备的数据发布设计为例,阐述互联网环境下HTTP客户端应用程序设计的基本方法。重点讲解工业物联网平台TLINK中HTTP设备的创建、通信数据帧的构造与测试。

不脱发的程序猿 1549

synchronized锁解析

线程的合理使用可以怎加cpu的利用率,达到增大系统吞吐量的效果。但是使用多线程的过程中会引发线程安全的问题。这篇文章我会尽量简介的阐述synchronized的使用及原理。

weixin_42563619的博客 170

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

15000字、6个代码案例、5个原理图让你彻底搞懂Synchronized

本篇文章围绕synchronized,深入浅出的描述CAS、synchronized在Java层面和C++层面的实现、锁升级原理、案例、源码等

关注菜菜的后端私房菜,获取更多技术干货~ 276

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

深入理解synchronized实现原理

synchronized关键字保证方法或者代码块在运行时,同一时刻只有一个方法可以进入临界区,同时它还可以保证共享变量的内存可见性。本文对于synchronized的原理进行详细的讲解,关于synchronized锁的升级将在后续的文章进行讲解。如有疑问请随时反馈。

zhaohuodian的博客 213

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

java八股文面试[多线程]——Synchronized的底层实现原理

ObjectWaiter对象),_owner指向持有ObjectMonitor对象的线程,当多个线程同时访问一段同步代码时,首先会进入_EntryList 集合,当线程获取到对象的monitor 后进入 _Owner 区域并把monitor中的owner变量。同时monitor中的计数器count加1,若线程调用 wait() 方法,将释放当前持有的monitor,owner变量恢复为null,count自减1,同时该线程进入 WaitSe t集合中。// 处于等待锁状态block状态的线程列表。

u200814342A的博客 609

精通Java并发锁机制:24种锁技巧+业务锁匹配方案(第一部分)

在 Java 并发编程中,锁是确保线程安全、协调多线程访问共享资源的关键机制。从基本的 `synchronized` 同步关键字到高级的 `ReentrantLock`、读写锁 `ReadWriteLock`、无锁设计如 `AtomicInteger`,再到复杂的同步辅助工具如 `CountDownLatch`、`CyclicBarrier` 和 `Semaphore`,每种锁都针对特定的并发场景设计,以解决多线程环境下的同步问题。`StampedLock` 提供了乐观读锁和悲观写锁的选项,而 `Concu

肖哥弹架构博客 1573

java多线程 锁和Condition总结

注意:本文参考 java并发编程的艺术 从ReentrantLock的实现看AQS的原理及应用 | JavaGuide 重入锁ReentrantLock ReentrantLock简介 重入锁ReentrantLock, 顾名思义,就是支持重进入的锁,它表示该锁能够支持一个线程对 资源的重复加锁。除此之外,该锁的还支持获取锁时的公平和非公平性选择。 回忆在同步器一节中的示例(Mutex), 同时考虑如下场景当一个线程调用Mutex的lock() 方法获取锁之后,如果再次调用lock()方法...

今天吃了吗的博客 1813

Java锁:Lock与synchronized的全面对比

在Java版本中,synchronized经过大量优化,在低竞争环境下性能与Lock相当。提供了更大的灵活性和控制能力。理解两者的区别和适用场景,可以帮助开发者编写出更高效、更健壮的并发代码。各有优劣,选择哪种锁机制取决于具体需求。对于大多数简单场景,是更安全、更简洁的选择。当需要更高级的同步功能

whl 博客专栏 1028

多线程安全问题原理和解决办法,Synchronized和ReentrantLock使用与区别

线程安全问题概述 卖票问题分析 线程安全问题代码实现 线程安全问题原理分析 解决线程安全问题办法1-synchronized同步代码块 同步技术原理分析 解决线程安全问题办法2-synchronized普通同步方法 解决线程安全问题办法3-synchronized静态同步方法 解决线程安全问题办法4-Lock锁 ReentrantLock和Synchronized的区别

write less , do more 1万+

深入分析synchronized的实现原理

记得刚刚开始学习Java的时候,一遇到多线程情况就是synchronized,相对于当时的我们来说synchronized是这么的神奇而又强大,那个时候我们赋予它一个名字“同步”,也成为了我们解决多线程情况的百试不爽的良药。但是,随着我们学习的进行我们知道synchronized是一个重量级锁,相对于Lock,它会显得那么笨重,以至于我们认为它不是那么的高效而慢慢摒弃它。 诚然,随着Javs SE...

weixin_38927257的博客 238

Java线程同步与锁

一、synchronized     synchronized锁什么?锁对象。可能锁对象包括: this, 临界资源对象,Class类对象。 1,同步方法   synchronized T methodName(){}   同步方法锁定的是当前对象。当多线程通过同一个对象引用多次调用当前同步方法时,需同步执行。 2,同步代码块       T methodName(){s...

weixin_30782293的博客 83
上一篇: 老生常谈系列一-----------HashMap底层原理(源码分析)
AY_G
博客等级 码龄7年 330粉丝 5原创
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值