Synchronized的实现和锁升级

1.JVM是如何处理和识别Synchronized的?

我们从字节码角度分析synchronized的实现:

  1. Synchronized(锁对象){}同步代码块底层实现方式是monitorentermonitorexit指令。

  2. 修饰普通同步方法时底层实现方式是执行指令会检查方法是否设置ACC_SYNCHRONIZED,如果设置了,则会先持有monitor锁(其实就是管程,锁对象),然后在执行方法,最后释放锁(无论方法执行完或出现异常)。

  3. 修饰静态同步方法时底层实现方式是执行指令会检查方法是否同时设置ACC_STATIC和ACC_SYNCHRONIZED,ACC_STATIC也用于分辨锁是类锁还是对象锁

2.为什么任何一个类的对象都可以成为锁对象?

        在HotSpot虚拟机中,监视器monitor采用的是ObjectMonitor实现的,在Java中,Object是每个类的父类,所以每个对象天生都带着一个对象监视器。在ObjectMonitor.java源代码中我们发现里面调用了objectMonitor.cpp文件,在objectMonitor.cpp里面又调用了ObjectMonitor.hpp,而在hpp文件中很明确的记录了正在持有此锁的线程、锁的重入次数等数据。

 

3.Synchronized锁的升级

Synchronized锁的状态主要依赖对象头中的MarkWord中锁标志位偏向锁位

3.1下面我们就表述锁升级的过程(重点)

        初始状态下,一个对象被实例化后,如果还没有任何线程使用此锁,那么它就为无锁状态(偏向锁位为0,锁标志位为01),当线程A第一次占用此锁时,MarkWord中会记录线程A的线程id,然后升级为偏向锁(偏向锁位为1,锁标志位为01),然后下一个线程访问时,会看MarkWord中记录的线程id是否和访问的线程一致,如果一致,就相当于还是线程A一直在访问,那么就会自动的获取锁,无需每次CAS去更新对象头,但是如果发现线程的id不一致,那么就发生了竞争,比如线程B来访问了,发现MarkWord中记录的线程id和自己的不一致,那么就会尝试使用CAS来替换MarkWord里面的线程id为自己线程B的线程id,如果修改竞争成功了,那么ok,MarkWord里面的线程idA更换为线程B的id,锁不会升级,还是偏向锁,但是如果线程B修改竞争失败,那么锁的状态就需要发生改变了,首先就是要先撤销偏向锁,先等待全局的安全点(STW),同时检查正持有偏向锁的线程A执行到哪里了,如果说线程A正在处于同步代码块中,相当于线程A还没有执行完,那么会将锁升级为轻量锁(偏向锁位为0,锁标志位为00),线程A继续执行同步代码块,而正在竞争的线程B会自动进行自旋。但如果说线程A刚好执行完同步代码块,此时会设置为无锁的状态,线程A,B会同时开始竞争。如下图:

 ​​​​​​

        假如此时锁升级为了轻量级锁,JVM会在每个线程的栈帧中创建用于存储锁记录的空间(Displaced Mark Word),若此时线程A想要获取轻量级锁,会把锁对象的MarkWord拷贝复制到自己的DMW里面,然后线程A再尝试利用CAS将锁对象中的MarkWord中的指向记录改为指向线程A栈中的Lock Record的指针,此时如果线程A的CAS失败了,就说明线程B正在占用此锁,线程A就会通过不断自旋来获取锁,等到线程B执行完后,线程B还要将轻量级锁释放,线程B使用CAS操作将DMW的内容重新复制回锁对象的Mark Word里面。如果此时有大量的线程涌入,参与竞争,一个线程自旋到一定的次数,锁就会会升级为重量级锁(偏向锁位为0,锁标志位为10),没拿到锁的线程会等待操作系统的调动,就不在主动的去抢占获取锁了。具体这个自旋次数在Java8之后是自适应自旋锁。

  • 线程如果自旋成功了,那下次自旋的最大次数会增加,因为JVM认为既然上次成功了,那么这一次也大概率会成功

  • 如果很少会自选成功,那么下次会减少自旋的次数甚至不自旋,避免CPU空转。

3.2几个需要说明的小问题?

1.JDK15废除了偏向锁

JDK15以后逐步废弃偏向锁,需要手动开启------->因为维护成本高。

2.MarkWord中指向记录在不同状态的指向不同

  • 偏向锁:MarkWord存储的是偏向的线程ID

  • 轻量锁:MarkWord存储的是指向线程栈中Lock Record的指针

  • 重量锁:MarkWord存储的是指向堆中锁的Monitor(监视器)对象,修改里面的owner来实现。

3.无锁会默认到偏向锁

实际上无锁是默认会自动升级为偏向锁的,但是启动时间有延迟,可以通过添加参数,让其在程序启动时立即启动。

4.锁升级后,hashcode去哪里了?

我们可以发现,hashcode值的位置和锁指向的内存位置会冲突,那么内部是怎么解决的呢——>

  • 在无锁的状态下,对象的hashcode()值存储在Mark Word中,此时它就再也无法进入到偏向锁状态了。

  • 如果已经在偏向锁状态下,才调用hashcode()方法,偏向锁的状态会被立即取消,锁会膨胀为重量级锁。

  • 在轻量级锁状态下,会在DMW中保存拷贝的Mark Word的值,释放锁后,会将这些信息重新写回到对象头的Mark Word中(相当于覆盖了)。

  • ObjectMonitor类里面有字段会记录非加锁状态下的Mark Word,锁释放后也会重新写回到对象头中的Mark Word中。

3.3JIT编译器对锁的优化

JIT对锁的优化分为锁消除和锁粗化,其实这两个概念挺乏味的。

3.3.1锁消除

简单来说就是,如果每个线程都拥有一把锁,那么我们写的加锁代码就毫无意义了,从JIT角度来看就是无视它了,消除了对锁的使用。示例代码:

public class LockClearUpDemo {static Object object = new Object();public void m1() {//锁消除问题,JIT会无视它,synchronized(o)每次new出来的,加锁就无意义了Object o = new Object();synchronized (o) {System.out.println("-----------hello LockClearUpDemo" + "\t" + o.hashCode() + "\t" + object.hashCode());}}public static void main(String[] args) {LockClearUpDemo lockClearUpDemo = new LockClearUpDemo();for (int i = 0; i < 10; i++) {new Thread(() -> {lockClearUpDemo.m1();}, String.valueOf(i)).start();}}
}

3.3.2锁粗化

如方法中多个同步块首尾相接,前后使用的都是同一个锁对象,那么JIT编译器会把这几个synchronized块合并为一个大块,加粗锁的范围。

public class LockBigDemo {static Object objectLock = new Object();public static void main(String[] args) {new Thread(() -> {synchronized (objectLock) {System.out.println("111111111111");}synchronized (objectLock) {System.out.println("222222222222");}synchronized (objectLock) {System.out.println("333333333333");}synchronized (objectLock) {System.out.println("444444444444");}//底层JIT的锁粗化优化synchronized (objectLock) {System.out.println("111111111111");System.out.println("222222222222");System.out.println("333333333333");System.out.println("444444444444");}}, "t1").start();}
}

4.Synchronized的具体实现

        线程代码进入到Synchronized代码块时会自动获取锁对象,这时其他线程访问时会被阻塞,直到Synchroinzed代码块执行完毕或抛出异常,调用wait()方法都会释放锁对象。在进入Synchronized代码块时会将主内存的变量读取到自己的工作内存,在退出的时候会把工作内存的更新值写入到主内存。Java中Synchronized通过在锁对象的对象头设置标记,达到获取锁和释放锁的目的。

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

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

相关文章

从0到1基于ChatGLM-6B使用LoRA进行参数高效微调

从0到1基于ChatGLM-6B使用LoRA进行参数高效微调 吃果冻不吐果冻皮 ​ 关注他 cliniNLPer 等 189 人赞同了该文章 ​ 目录 收起 ChatGLM-6B简介 具备的一些能力 局限性 LoRA 技术原理 环境搭建 数据集准备 数据预处理 参数高效微调 单卡模式模型训练 数据并行模式模型训练 模型推…

Nginx详细学习记录

1. Nginx概述 Nginx是一个轻量级的高性能HTTP反向代理服务器&#xff0c;同时它也是一个通用类型的代理服务器&#xff0c;支持绝大部分协议&#xff0c;如TCP、UDP、SMTP、HTTPS等。 1.1 Nginx基础架构 Nginx默认采用多进程工作方式&#xff0c;Nginx启动后&#xff0c;会运行…

阿里云RDS关系型数据库详细介绍_多版本数据库说明

阿里云RDS关系型数据库大全&#xff0c;关系型数据库包括MySQL版、PolarDB、PostgreSQL、SQL Server和MariaDB等&#xff0c;NoSQL数据库如Redis、Tair、Lindorm和MongoDB&#xff0c;阿里云百科分享阿里云RDS关系型数据库大全&#xff1a; 目录 阿里云RDS关系型数据库大全 …

编译工具链 之二 详解 ELF 格式及标准、UNIX 发展、ABI

在计算机及嵌入式系统中&#xff0c;二进制文件也有一定的标准格式&#xff0c;通常会包含在各平台的应用程序二进制接口 &#xff08;Application Binary Interface&#xff0c;ABI&#xff09;规范中。它是编译工具链必须要遵守的规范&#xff08;编译工具链产生符合 ABI 的二…

C (1094) : DS双向链表—前驱后继

Description 在双向链表中&#xff0c;A有一个指针指向了后继节点B&#xff0c;同时&#xff0c;B又有一个指向前驱节点A的指针。这样不仅能从链表头节点的位置遍历整个链表所有节点&#xff0c;也能从链表尾节点开始遍历所有节点。 对于给定的一列数据&#xff0c;按照给定的…

Legion Y9000X IRH8 2023款(82Y3)原装出厂OEM预装Windows11系统

lenovo联想电脑笔记本拯救者原厂win11系统镜像 下载链接&#xff1a;https://pan.baidu.com/s/15G01j7ROVqOFOETccQSKHg?pwdt1ju 系统自带所有驱动、出厂主题壁纸、Office办公软件、联想电脑管家等预装程序 所需要工具&#xff1a;32G或以上的U盘 文件格式&#xff1a;ISO…

创作2周年?浅记一下~

前言&#xff1a; 最近确实有点缺乏去更新博客的动力&#xff0c;一晃两年过去了&#xff0c;其实也是我新入职公司的两年&#xff0c;两年虽然不长&#xff0c;但是确实发生了太多事情值得去记录下来... 机缘 说是机缘也不是算是&#xff0c;第一次写博客是刚好在CSDN里面查资…

CentOS如何查找java安装路径

目 录 背景 详细步骤 1.使用指令查看有关javad安装路径 2.填入java路径 3.查找java安装路径 4.配置文件展示 背景 准备部署分布式hadoop的时候&#xff0c;校验hadoop版本发现java没配置 但是又有java版本信息 详细步骤 1.使用指令查看有关javad安装路径 java -verb…

高级IO(Linux)

高级IO 五种IO模型高级IO重要概念同步通信 vs 异步通信阻塞 vs 非阻塞 非阻塞IOfcntl实现函数SetNoBlock轮询方式读取标准输入 I/O多路转接之select初识selectselect函数原型参数解释参数timeout取值关于fd_set结构关于timeval结构函数返回值三级目录 理解select执行过程socket…

微信小程序——CSS3渐变

SS3 渐变&#xff08;gradients&#xff09;可以在两个或多个指定的颜色之间显示平稳的过渡。CSS3 定义了两种类型的渐变&#xff08;gradients&#xff09;&#xff1a; 说明 1、线性渐变&#xff08;Linear Gradients&#xff09;- 向下/向上/向左/向右/对角方向&#xff1…

Java版本企业电子招投标采购系统源码之项目说明和开发类型源码

项目说明 随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大&#xff0c;公司对内部招采管理的提升提出了更高的要求。在企业里建立一个公平、公开、公正的采购环境&#xff0c;最大限度控制采购成本至关重要。符合国家电子招投标法律法规及相关规范&#xff0c;以及…

练[FBCTF2019]RCEService

[FBCTF2019]RCEService 文章目录 [FBCTF2019]RCEService掌握知识解题思路关键paylaod 掌握知识 ​ json字符串格式&#xff0c;命令失效(修改环境变量)–绝对路径使用linux命令&#xff0c;%0a绕过preg_match函数&#xff0c;代码审计 解题思路 打开题目链接&#xff0c;发现…

Python爬虫——爬虫基础模块和类库(附实践项目)

一、简单介绍 Python爬虫是使用Python编程语言开发的一种自动化程序&#xff0c;用于从互联网上获取信息。通过模拟浏览器的行为&#xff0c;爬虫可以访问网页、解析网页内容&#xff0c;并提取所需的数据。 python的爬虫大致可以分为通用爬虫和专用爬虫&#xff1a; 通用爬虫…

【Linux】Vim使用总结

【Linux】Vim使用总结 Vim 的三种模式命令行模式1. 移动2.复制&#xff0c;粘贴&#xff0c;剪切3.撤销4.大小写切换&#xff0c;替换&#xff0c;删除 插入模式底行模式 Vim 的三种模式 一进入VIM就是处于一般模式&#xff08;命令模式&#xff09;&#xff0c;该模式下只能输…

ES 关于 remote_cluster 的一记小坑

最近有小伙伴找到我们说 Kibana 上添加不了 Remote Cluster&#xff0c;填完信息点 Save 直接跳回原界面了。具体页面&#xff0c;就和没添加前一样。 我们和小伙伴虽然隔着网线但还是进行了深入、详细的交流&#xff0c;梳理出来了如下信息&#xff1a; 两个集群&#xff1a;…

架构师-软件工程习题选择题

架构师-软件工程习题选择题

不同数据类型在单片机内存中占多少字节?

文章目录 前言一、不同编译器二、C51* 指针型 三、sizeof结构体联合体 前言 在C语言中&#xff0c;数据类型指的是用于声明不同类型的变量或者函数的一个广泛的系统。变量的类型决定了变量存储占用的空间 一、不同编译器 类型16位编译器大小32位编译器大小64位编译器大小char…

HTTPS工作过程,国家为什么让http为什么要换成https,Tomcat在MAC M1电脑如何安装,Tomcat的详细介绍

目录 引言 一、HTTPS工作过程 二、Tomcat 在访达中找到下载好的Tomcat文件夹&#xff08;这个要求按顺序&#xff09; zsh: permission denied TOMCAT的各部分含义&#xff1a; 引言 在密码中一般是&#xff1a;明文密钥->密文&#xff08;加密&#xff09; &#xff…

机器学习笔记 - 深入研究spaCy库及其使用技巧

一、简述 spaCy 是一个用于 Python 中高级自然语言处理的开源库。它专为生产用途而设计,这意味着它不仅功能强大,而且快速高效。spaCy 在学术界和工业界广泛用于各种 NLP 任务,例如标记化、词性标注、命名实体识别等。 安装,这里使用阿里的源。 pip install spacy…

三十二、【进阶】hash索引结构

1、hash索引结构 &#xff08;1&#xff09;简述&#xff1a; hash索引&#xff0c;就是采用一定的hash算法&#xff0c;将键值换算成新的hash值&#xff0c;映射到对应的槽位上&#xff0c;然后存储在hash表中。 &#xff08;2&#xff09;图示&#xff1a; 2、hash索引结构…