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.rhkb.cn/news/153458.html

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

相关文章

登陆认证权限控制(1)——从session到token认证的变迁 session的问题分析 + CSRF攻击的认识

前言 登陆认证&#xff0c;权限控制是一个系统必不可少的部分&#xff0c;一个开放访问的系统能否在上线后稳定持续运行其实很大程度上取决于登陆认证和权限控制措施是否到位&#xff0c;不然可能系统刚刚上线就会夭折。 本篇博客回溯登陆认证的变迁历史&#xff0c;阐述sess…

如何打造一个网络框架模块对接服务器

一、了解网络框架的基本原理 在开始打造网络框架模块之前&#xff0c;首先需要了解网络框架的基本原理。网络框架是一个软件模块&#xff0c;用于处理网络通信的各种细节&#xff0c;包括数据传输、协议解析、错误处理等。常见的网络框架有HTTP、TCP/IP、WebSocket等。 对啦&…

在Remix中编写你的第一份智能合约

智能合约简单来讲就是&#xff1a;部署在去中心化区块链上的一个合约或者一组指令&#xff0c;当这个合约或者这组指令被部署以后&#xff0c;它就不能被改变了&#xff0c;并会自动执行&#xff0c;每个人都可以看到合约里面的条款。更深层次的理解就是&#xff1a;这些代码会…

微信小程序案例:2-2本地生活

文章目录 一、实现步骤&#xff08;一&#xff09;创建项目&#xff08;二&#xff09;创建页面&#xff08;三&#xff09;准备图片素材&#xff08;四&#xff09;编写页面结构1、编写轮播区域页面结构2、编写九宫格区域页面结构 &#xff08;五&#xff09;编写页面样式1、编…

Redis-持久化机制

持久化机制介绍 RDBAOFRDB和AOF对比 RDB rdb的话是利用了写时复制技术&#xff0c;他是看时间间隔内key值的变化量&#xff0c;就比如20秒内如果有5个key改变过的话他就会创建一个fork子进程&#xff08;bgsave&#xff09;&#xff0c;通过这个子进程&#xff0c;将数据快照进…

Golang网络编程:即时通讯系统Instance Messaging System

系统基本架构 版本迭代 项目改造 无人机是client&#xff0c;我们是server&#xff0c;提供注册登入&#xff0c;场景选择等。信道模拟器是server&#xff0c;我们是client&#xff0c;我们向信道模拟器发送数据&#xff0c;等待信道模拟器计算结果&#xff0c;返回给无人机。…

MFC 鼠标悬停提示框

MFC 鼠标悬停提示框 运行效果 在MFC窗口中添加一个控件 工具栏中拖拽List Box到MFC窗口给List Box添加变量 CListBox m_listbox 增加成员变量 CWnd* m_tip_parent_wnd; CToolTipCtrl m_tip;给m_listbox创建提示框 void create_tip_window(CWnd* tip_wnd, CToolTipCtrl* ti…

[nltk_data] Error loading stopwords: <urlopen error [WinError 10054]

报错提示&#xff1a; >>> import nltk >>> nltk.download(stopwords) 按照提示执行后 [nltk_data] Error loading stopwords: <urlopen error [WinError 10054] 找到路径C:\\Users\\EDY\\nltk_data&#xff0c;如果没有nltk_data文件夹&#xff0c;在…

Magica Cloth 使用方法笔记

Magica Cloth 使用方法笔记 官方使用文档链接&#xff1a; インストールガイド – Magica Soft 鱼儿效果案例&#xff1a; https://www.patreon.com/posts/69459293 安装环境&#xff1a; 关于在Unity 2018.4.12版本 下 导入 Magic Cloth 之前&#xff0c;需要提前置入的…

FreeRTOS学习笔记(一)

一、基础知识思维导图 vtaskdelay函数会开启中断&#xff0c;所以在临界区不能用vtaskdelay 二、任务的创建与删除 2.1、任务的动态创建与删除 ........#define START_TASK_PRIO 1 #define START_TASK_STACK_SIZE 128 TaskHandle_t start_task_handler; void …

springboot项目集成kafka,并创建kafka生成消息线程池

效果图: 步骤1:添加依赖 <!-- kafka依赖 --><dependency><groupId>org.apache.kafka</groupId><<

vue-slot插槽

作用&#xff1a;让父组件可以向子组件中任意位置插入html结构&#xff0c;也是组件通信方式的一种&#xff0c;适用于父组件》子组件 分类: 默认插槽、具名插槽、作用域插槽 定义子组件时使用slot组件&#xff0c;在使用子组件是可以决定是否插入具体的html代码 默认插槽 如…

【无公网IP内网穿透】基于NATAPP搭建Web站点

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是Java方文山&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的专栏《.内网穿透》。&#x1f3af;&#x1f3af; &#…

RustDay01——运行在线GitHub Rust环境

1.跟着教程进入GitHub教室 2. 授权确认后进入学习空间 3.点击链接进入在线平台 4.添加本机密钥对到GitHub 5. 安装依赖 我们使用在线的Linux试验平台&#xff0c;就自动帮我们clone好了仓库 我们直接在仓库目录执行 cargo install --force --path . 安装依赖 PS:其实刚开始…

数据结构 | (三) Stack

栈 &#xff1a;一种特殊的线性表&#xff0c;其 只允许在固定的一端进行插入和删除元素操作 。 进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO &#xff08; Last In First Out &#xff09;的原则。 压栈&#xff1a;栈…

在Kubernetes中实现gRPC流量负载均衡

在尝试将gRPC服务部署到Kubernetes集群中时&#xff0c;一些用户&#xff08;包括我&#xff09;面临的挑战之一是实现适当的负载均衡。在深入了解如何平衡gRPC的方式之前&#xff0c;我们首先需要回答一个问题&#xff0c;即为什么需要平衡流量&#xff0c;如果Kubernetes已经…

苹果遭遇安全危机,应用商店曝出不良APP,或影响iPhone的销售

据澎湃新闻报道指苹果的App Store被曝出不良APP位居下载榜前列&#xff0c;这对于向来强调APP严格审核的苹果来说是巨大的打击&#xff0c;更影响向来被认为信息安全遥遥领先的名声&#xff0c;对当下正热销的iPhone15或造成打击。 据了解被曝的软件以“学习XX字母”为命名&…

2020架构真题(四十六)

、以下关于操作系统微内核架构特征的说法&#xff0c;不正确的是&#xff08;&#xff09;。 微内核的系统结构清晰&#xff0c;利于协作开发微内核代码量少&#xff0c;系统具有良好的可移植性微内核有良好的的伸缩性和扩展性微内核功能代码可以互相调用&#xff0c;性能很高…

Unity官方文档中关于内存管理的翻译(2021.3)

原文:Memory in Unity - Unity 手册 Unity内存管理 为了确保您的应用程序运行时没有性能问题&#xff0c;了解Unity如何使用和分配内存非常重要。本文档的这一部分解释了Unity中内存是如何工作的&#xff0c;适用于希望了解如何提高应用程序内存性能的读者。 Unity使用三个内…

第二证券:汽车产业链股活跃,恒勃股份、博俊科技“20cm”涨停

轿车产业链股9日盘中走势活跃&#xff0c;截至发稿&#xff0c;恒勃股份、博俊科技“20cm”涨停&#xff0c;德迈仕涨超17%&#xff0c;上声电子涨超14%&#xff0c;川环科技涨超10%&#xff0c;圣龙股份、科华控股、沪光股份、上海沿浦、日盈电子、赛力斯等均涨停。 工作方面…