Synchronized锁详解(全网最细)

目录

以下知识基于HotSpot虚拟机实现

1.前置知识

1.1 锁的作用

1.2 Java中常见的锁类型

1.3 锁的重入

2.使用场景

2.1 修饰实例方法

2.1.1 用法

2.1.2 原理

2.1.3 特点

2.2 修饰静态方法

2.2.1 用法

2.2.2 原理

2.3 修饰代码块

2.3.1 用法

3.原理

3.1 对象锁

3.1.1 对象锁的实现原理

3.1.1.1 无锁

3.1.1.2 偏向锁

3.1.1.3 轻量级锁

3.1.1.4 重量级锁

3.1.1.2.1 Monitor

3.1.2 锁升级

3.1.3 锁消除

3.1.4 锁粗化

3.2 对象头

3.2.1 对象头的结构

3.2.1.1 MarkWord

PS:这里分代年龄是4位bit,也说明了为什么分代年龄最大是15。

3.2.1.1 指向类的指针(class pointer)


以下知识基于HotSpot虚拟机实现

1.前置知识

1.1 锁的作用

锁是一种同步机制,可以用来协调多个线程的并发访问,以保证对共享资源的安全访问。可以理解为防止一件东西同时被多个人使用。

1.2 Java中常见的锁类型

Java中常见的锁类型-CSDN博客

1.3 锁的重入

锁的重入是指在一个线程持有锁的情况下,可以重复获取同一个锁,而不会发生死锁。正常来说a获取到锁后b不能获取锁,但是当一个实例内的a方法和b方法都是用一把锁上锁时,同一个线程访问a和b就需要重入的能力,比如在a方法中访问b方法,那么a的锁x已经被抢占了,如果不支持重入,那么访问b的时候会发现x锁已经被自己抢占而无法访问,支持重入的原理也就是在这里判断一下当前方法的锁是否是本线程拥有的锁。

2.使用场景

2.1 修饰实例方法

2.1.1 用法
public class SynchronizedExample {// 使用synchronized修饰的实例方法public synchronized void increment() {}
}
2.1.2 原理

Java通过锁住此方法对应的对象锁实现同步访问。具体查看3.1

2.1.3 特点

由于锁定的是对象锁,那么会有一下特点

1.同一个实例对象下被Synchronized修饰的方法会互相影响,也就是说当访问同一个实例对象中被Synchronized修饰的a方法时,其他线程无法访问这个实例对象下被被Synchronized修饰的b方法。

2.被Synchronized修饰的方法与未被Synchronized修饰的方法互不影响

3.但是如果是同一线程访问a方法后和b方法,不会被阻塞,因为Synchronized支持重入(查看1.3)

2.2 修饰静态方法

2.2.1 用法
synchronized void staic method() {
}
2.2.2 原理

Java通过锁住此方法对应的类的对象锁实现同步访问。具体查看3.1

静态同步方法, 锁是当前类的Class对象。一个类只有一个类对象,类对象的锁也是和3.1对象锁原理一样

2.3 修饰代码块

2.3.1 用法
synchronized(this) {
}

锁是synchronized括号里实例的对象。

synchronized(a.class) {
}

锁是synchronized括号里类的class对象。

3.原理

这里需要一些前置知识,每一个对象可以关联一个ObjectMonitor对象,对象中有个对象头的区域,Java在不同场景下通过利用对象头以及ObjectMonitor对象来实现对象锁。

3.1 对象锁

JVM在JDK1.6后会有几种状态,线程获取对象时,分别会判断对象锁的状态,以此来决定线程是否进入阻塞,或者获取到锁对象执行代码块,或者自旋等接下来的动作。

对象锁是Java中用于实现同步的机制之一,它可以确保在多线程环境下对共享资源的访问是安全的。对象锁的重量级锁状态是基于对象的监视器(monitor)实现的,在Java中,每个对象都有一个与之关联的监视器,用于实现同步

3.1.1 对象锁的实现原理

最开始JDK1.5以及以前,java只有重量级锁。

从JDK1.6开始,对象锁有4种状态,无锁,偏向锁,轻量级锁,重量级锁。

这四种状态由3.2.1.1 中的MarkWord内的是否偏向锁或锁标志位确定

先了解四种状态的特点

3.1.1.1 无锁

当MarkWord的是否偏向锁位为0,锁标志位为01时。对象锁为无锁状态

3.1.1.2 偏向锁

当MarkWord的是否偏向锁位为1,锁标志位为01时。对象锁为偏向锁状态

当偏向锁开启状态时,一个线程需要获取对象锁时,需要将Markword的偏向线程id位修改为自己的线程id。修改方式为CAS操作。当有其他线程访问此对象锁时,偏向锁升级为轻量级锁。

 偏向锁的释放:

偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动去释放偏向锁。偏向锁释放后就会升级为轻量级锁。

3.1.1.3 轻量级锁

当MarkWord的锁标志位为01时。对象锁为轻量级状态

线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。然后线程尝试使用 CAS将对象头中的Mark Word替换为指向锁记录的指针如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁,如果自旋一定次数(默认为10,可以通过参数-XX:PreBlockSpin来调整。--XX:+UseSpinning参数来开启自旋(JDK1.6之前默认关闭自旋))后依旧获取不到锁,轻量级锁会膨胀为重量级锁,且当前自旋的线程会阻塞。很多博客对这里说得含糊不清,需要注意。

轻量级锁中,某进程cas失败后自旋的意义是为了减少线程从用户态到内核态的上下文切换。CPU对这两种状态的切换比较耗时

补充:

自适应自旋锁

​ JDK 1.6引入了更加聪明的自旋锁,即自适应自旋锁。所谓自适应就意味着自旋的次数不再是固定的,它是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。

3.1.1.4 重量级锁

当MarkWord的锁标志位为10时。对象锁为重量级状态,对象锁进入重量级状态后,将采用monitor的方式加锁和释放锁。

3.1.1.4.1 Monitor

对象锁在重量级状态时,是通过进入、退出 对象监视器(Monitor) 来实现对方法、代码块的同步的,而对象监视器的本质依赖于底层操作系统的 互斥锁(Mutex Lock) 实现。

互斥锁(Mutex Lock)

一种常见的线程同步机制,用于保护共享资源在多线程环境下的互斥访问。它提供了两个基本操作:加锁(Lock)和解锁(Unlock)。

互斥锁的原理和实现方式可以有多种,常见的实现包括使用原子操作、互斥变量、硬件指令等。下面是一个常见的互斥锁实现原理的简要分析:

    原子操作实现:原子操作是一种不可中断的操作,能够保证在多线程环境下的原子性。互斥锁的实现中,常用的原子操作是比较并交换(Compare and Swap,CAS)操作。具体实现中,互斥锁内部维护一个标志位,用于表示锁的状态。加锁操作通过原子的CAS操作将标志位从未锁定状态修改为锁定状态,如果修改成功则表示获取锁成功,否则需要重试。解锁操作将标志位恢复为未锁定状态。

    互斥变量实现:互斥变量是一种特殊的变量,它具有原子性操作和线程同步的特性。互斥锁的实现中,互斥变量被用作一个标志位,用于表示锁的状态。加锁操作通过原子的测试和设置操作来获取互斥变量的值,如果互斥变量的值为未锁定状态,则将其设置为锁定状态,表示获取锁成功。解锁操作将互斥变量的值恢复为未锁定状态。

    硬件指令实现:一些处理器架构提供了特定的硬件指令来支持互斥锁的实现。这些指令通常能够在单个指令级别上执行锁的加锁和解锁操作,具有较高的性能和效率。这些硬件指令可以保证锁的操作是原子的,从而实现线程的同步和互斥访问。

代码块的加锁:

字节码的入口和出口分别有monitorentermonitorexit指令。当执行monitorenter指令时,线程试图获取锁也就是获取monitor(monitor对象存在于每个Java对象的对象头中,synchronized锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因)的持有权。当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行monitorexit指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。大部分博客都是如是说,但是这里不准确,对象头中存放的是指向重量级锁的指针,也就是之前提到的每一个对象可以关联的那个ObjectMonitor对象。

方法的加锁:

字节码中是添加ACC_SYNCHRONIZED标识,该标识指明了该方法是一个同步方法,JVM通过该ACC_SYNCHRONIZED访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。

虽然字节码的标识不一样,但是都是会获取Monitor对象去做操作。

monitor的结构:

刚开始 Monitor 中 Owner 为 null


当 Thread-2 执行 synchronized(obj) 就会将Monitor的所有者 Owner 置为Thread-2 ,Monitor中只能有一个Owner


在Thread-2上锁的过程中,如果Thread-3,Thread-4,Thread-5也来执行synchronized(obj),就会进入EntryList BLOCKED


Thread-2执行完同步代码块中的内容,然后唤醒EntryList中等待的线程来竞争锁,竞争时是非公平的


图中WaitSet中的Thread-0,Thread-1是之前获得过锁,但条件不满足进入WAITING状态的线程,在wait-notify中

3.1.2 锁升级

锁升级是在线程获取对象锁时,对象锁状态的一种切换。对象锁的状态也实时影响着获取对象锁的线程的状态,是否阻塞等。

手动开启或关闭偏向锁,你可以使用 -XX:+UseBiasedLocking -XX:-UseBiasedLocking 参数来控制。

需要注意的:

开启偏向锁的时候,对象锁只有 偏向锁->轻量级锁->重量级锁升级流程

关闭偏向锁的时候,对象锁只有 无锁->轻量级锁->重量级锁升级流程

3.1.3 锁消除

​ 为了保证数据的完整性,我们在进行操作时需要对这部分操作进行同步控制,但是在有些情况下,JVM检测到不可能存在共享数据竞争,这是JVM会对这些同步锁进行锁消除。锁消除的依据是逃逸分析的数据支持。

  如果不存在竞争,为什么还需要加锁呢?

  所以锁消除可以节省毫无意义的请求锁的时间。变量是否逃逸,对于虚拟机来说需要使用数据流分析来确定,但是对于我们程序员来说这还不清楚么?我们会在明明知道不存在数据竞争的代码块前加上同步吗?但是有时候程序并不是我们所想的那样?

  我们虽然没有显示使用锁,但是我们在使用一些JDK的内置API时,如StringBuffer、Vector、HashTable等,这个时候会存在隐形的加锁操作。

逃逸分析:

Java - 深入理解Java中的逃逸分析_java doescapeanalysis-CSDN博客

3.1.4 锁粗化

就是将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁。

3.2 对象头

对象有三块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

3.2.1 对象头的结构

对象头由三部分组成:MarkWord,指向类的指针,​数组长度​(只有数组对象才有)

3.2.1.1 MarkWord

Mark Word记录了对象和锁有关的信息,当这个对象被synchronized关键字当成同步锁时,围绕这个锁的一系列操作都和Mark Word有关。

Mark Word在32位JVM中的长度是32bit,在64位JVM中长度是64bit。

Mark Word在不同的锁状态下存储的内容不同,在32位JVM中是这么存的:

Mark Word在不同的锁状态下存储的内容不同,在64位JVM中是这么存的:

PS:这里分代年龄是4位bit,也说明了为什么分代年龄最大是15。
3.2.1.1 指向类的指针(class pointer)

在看此部分时候,需要提前了解,实例对象类的class对象的区别,是三个东西。

即指向方法区的instanceKlass实例 (虚拟机通过这个指针来确定这个对象是哪个类的实例。)

上图是JDK1.6的状态。

1.7和1.8中class的实例对象是放在堆中了

具体可查看这篇博客,类的class实例对象存放位置:

https://www.cnblogs.com/xy-nb/p/6773051.html

参考博客:

Java的对象头和对象组成详解_hotspot虚拟机中java对象结构的图示-CSDN博客

https://www.cnblogs.com/thiaoqueen/p/9314745.html

开启偏向锁一定性能更好吗?_jvm偏向锁导致性能问题-CSDN博客

Java - 深入理解Java中的逃逸分析_java doescapeanalysis-CSDN博客

Synchronized 关键字原理-CSDN博客

synchronized详解-CSDN博客

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

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

相关文章

数字电路(四,五章总结)

四.组合逻辑电路设计 由波形图列真值表,之 后画出卡诺图,写出最简逻辑表达式。 卡诺图化简的时候圈住的部分如果某个字母有0又有1的话这个字母删掉,写出其他两个字母。 如下图中黄圈A有0又有1则删除A,这样黄圈代表BC;同理绿圈代…

SpringBoot项目基于java的教学辅助平台

采用技术 SpringBoot项目基于java的教学辅助平台的设计与实现~ 开发语言:Java 数据库:MySQL 技术:SpringBootMyBatis 工具:IDEA/Ecilpse、Navicat、Maven 页面展示效果 学生信息管理 教师信息管理 课程信息管理 科目分类管…

Pytorch入门实战 P06-调用vgg16模型,进行人脸预测

目录 1、本文内容: 1、内容: 2、简单介绍下VGG16: 3、相关其他模型也可以调用: 2、代码展示: 3、训练结果: 1、不同优化器: ①【使用SGD优化器】 ②【使用Adam优化器】 ③Adam 动态学…

(BERT蒸馏)TinyBERT: Distilling BERT for Natural Language Understanding

文章链接:https://arxiv.org/abs/1909.10351 背景 在自然语言处理(NLP)领域,预训练语言模型(如BERT)通过大规模的数据训练,已在多种NLP任务中取得了卓越的性能。尽管BERT模型在语言理解和生成…

深度学习 Lecture 7 迁移学习、精确率、召回率和F1评分

一、迁移学习(Transfer learning) 用来自不同任务的数据来帮助我解决当前任务。 场景:比如现在我想要识别从0到9度手写数字,但是我没有那么多手写数字的带标签数据。我可以找到一个很大的数据集,比如有一百万张图片的猫、狗、汽…

论文笔记:(INTHE)WILDCHAT:570K CHATGPT INTERACTION LOGS IN THE WILD

iclr 2024 spotlight reviewer 评分 5668 1 intro 由大型语言模型驱动的对话代理(ChatGPT,Claude 2,Bard,Bing Chat) 他们的开发流程通常包括三个主要阶段 预训练语言模型在被称为“指令调优”数据集上进行微调&…

JDK5.0新特性

目录 1、JDK5特性 1.1、静态导入 1.2 增强for循环 1.3 可变参数 1.4 自动装箱/拆箱 1.4.1 基本数据类型包装类 1.5 枚举类 1.6 泛型 1.6.1 泛型方法 1.6.2 泛型类 1.6.3 泛型接口 1.6.4 泛型通配符 1、JDK5特性 JDK5中新增了很多新的java特性,利用这些新…

v-for中涉及的key

一、为什么要用key? key可以标识列表中每个元素的唯一性,方便Vue高效地更新虚拟DOM;key主要用于dom diff算法,diff算法是同级比较,比较当前标签上的key和标签名,如果都一样,就只移动元素&#…

【刷题笔记】第七天

文章目录 [924. 尽量减少恶意软件的传播](https://leetcode.cn/problems/minimize-malware-spread/)方法一,并查集方法二,dfs [GCD and LCM ](https://vjudge.net.cn/problem/HDU-4497#authorKING_LRL) 924. 尽量减少恶意软件的传播 如果移除一个感染节…

上海计算机学会 2023年10月月赛 乙组T4 树的覆盖(树、最小点覆盖、树形dp)

第四题:T4树的覆盖 标签:树、最小点覆盖、树形 d p dp dp题意:求树的最小点覆盖集的大小和对应的数量,数量对 1 , 000 , 000 , 007 1,000,000,007 1,000,000,007取余数。所谓覆盖集,是该树的点构成的集合,…

docker 环境变量设置实现方式

1、前言 docker在当前运用的越来广泛,很多应用或者很多中间软件都有很多docker镜像资源,运行docker run 启动镜像资源即可应用。但是很多应用或者中间件有很多配置参数。这些参数在运用过程怎么设置给docker 容器呢?下面介绍几种方式 2 、do…

无线网络安全之WiFi Pineapple初探

背景 WiFi Pineapple(大菠萝)是由国外无线安全审计公司Hak5开发并售卖的一款无线安全测试神器。集合了一些功能强大的模块,基本可以还原钓鱼攻击的全过程。在学习无线安全时也是一个不错的工具,本文主要讲WiFi Pineapple基础配置…

和鲸科技将参与第五届空间数据智能学术会议并于应急减灾与可持续发展专题论坛做报告分享

ACM SIGSPATIAL中国分会致力于推动空间数据的研究范式及空间智能理论与技术在时空大数据、智慧城市、交通科学、社会治理等领域的创新与应用。ACM SIGSPATIAL中国分会创办了空间数据智能学术会议(SpatialDI),分会将于2024年4月25日-27日在南京…

javaWeb项目-快捷酒店管理系统功能介绍

项目关键技术 开发工具:IDEA 、Eclipse 编程语言: Java 数据库: MySQL5.7 框架:ssm、Springboot 前端:Vue、ElementUI 关键技术:springboot、SSM、vue、MYSQL、MAVEN 数据库工具:Navicat、SQLyog 1、Spring Boot框架 …

PSCAD|应用于输电线路故障测距的行波波速仿真分析

1 主要内容 该程序参考文献《应用于输电线路故障测距的行波波速仿真分析》,利用线路内部故障产生的初始行波浪涌达线路两端测量点的绝对时间之差值计算故障点到两端测量点之间的距离,并利用小波变换得到初始行波波头准确到达时刻,从而精准定…

富文本在线编辑器 - tinymce

tinymce 项目是一个比较好的富文本编辑器. 这里有个小demo, 下载下来尝试一下, 需要配置个本地服务器才能够访问, 我这里使用的nginx, 下面是我的整个操作过程: git clone gitgitee.com:chick1993/layui-tinymce.git cd layui-tinymcewget http://nginx.org/download/nginx-1.…

JavaEE:JVM

基本介绍 JVM:Java虚拟机,用于解释执行Java字节码 jdk:Java开发工具包 jre:Java运行时环境 C语言将写入的程序直接编译成二进制的机器语言,而java不想重新编译,希望能直接执行。Java先通过javac把.java…

RK3568 学习笔记 : 更改 u-boot spl 中的 emmc 的启动次序

环境 开发板: 【正点原子】 的 RK3568 开发板 ATK-DLRK3568 u-boot 版本:来自 【正点原子】 的 RK3568 开发板 Linux SDK,单独复制出来一份,手动编译 编译环境:VMware 虚拟机 ubuntu 20.04 问题描述 RK3568 默认 …

浅谈线程的生命周期

Java线程的生命周期是一个从创建到终止的过程,经历了多种状态的转变。在Java中,线程的生命周期可以划分为以下几个主要状态: 新建(New): 当使用 new Thread() 创建一个新的线程对象但尚未调用 start() 方法…

CSS基础之伪元素选择器(如果想知道CSS的伪元素选择器知识点,那么只看这一篇就足够了!)

前言:我们已经知道了在CSS中,选择器有基本选择器、复合选择器、伪类选择器、那么选择器学习完了吗?显然是没有的,这篇文章讲解最后一种选择器——伪元素选择器。 ✨✨✨这里是秋刀鱼不做梦的BLOG ✨✨✨想要了解更多内容可以访问我…