关于Java中synchronized的实现原理

news2024/12/23 16:59:53

并发编程的三个理念

  • 原子性:一个操作要么全部完成,要么全部失败。
  • 可见性:当一个线程对共享变量进行修改后,其他线程也应立刻看到。
  • 有序性:程序按照顺序执行

synchronized基本使用

  • 修饰静态方法,锁的是类,Class字节码对象
  • 修饰实例方法,锁的是当前实例对象
  • 修饰代码块,锁的是当前指定的对象

原理

在JDK1.6之前,synchronized是重量级锁,是独占锁,在JDK6中,引入了偏向锁和轻量级锁,同时synchronized支持锁升级,降低了synchronized的性能消耗。
我们以synchronized的重量级锁为例,来讲解原理。

同步代码块

当一个线程访问同步代码块时,首先要获取到锁才能执行同步代码块,当退出或抛出异常时必须要释放锁
我们这里有一个同步代码块

public void add() {
	synchronized (this) {
		int i = 1;
		int b = i + 3;
	}
}

利用javap反编译看这段代码的字节码指令

可以看到,synchronized是通过monitorenter和monitorexit这一组字节码指令来完成对临界资源的互斥访问

  • monitorenter标志进入同步代码块
  • monitorexit标志退出同步代码块。

我们知道,任意一个对象都可以作为锁对象。
每个锁对象都有一个监视器,叫做Monitor,当Monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取Monitor的使用权,过程如下

  1. 如果Monitor的进入数是0,则该线程进入Monitor,然后将进入量设置为1,该线程即为Monitor的所有者。
  2. 如果线程已经占用了该Monitor,只是重新进入,则进入Monitor的进入数加1
  3. 如果其他线程已经占用了Monitor,则该线程进入阻塞状态,直到Monitor的进入数为0,在重新尝试获取Monitor的所有权。

Monitor是操作系统中管程的一个实现,管程是操作系统中对同步互斥的一种实现方案,建议先去看看管程。

执行monitorexit的线程必须是锁对象所对应的Monitor的所有者,线程执行monitorexit指令的过程:
monitorexit执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出Monitor,不再是这个Monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个monitor的所有权。

这一组指令必须是成对出现的,不能单独出现,这两个指令是通过操作系统互斥原语mutex来实现的,被阻塞的线程会被挂起、等待重新调度,会导致用户态和内核态之间的来回切换,性能损耗严重。

总结,synchronized底层是通过一个Monitor监视器对象来实现的,线程只有抢占到了Monitor对象的所有权,才有权获取到临界资源,线程之间的wait/notify等方法也是依赖于monitor对象,这就是为什么只有在同步代码块中执行wait/notify,否则抛出异常

同步方法

对于同步方法,即在方法上使用synchronized关键字修饰,在反编译后,并没有看到monitorenter和monitorexit这一组指令。
这是一个同步方法

public synchronized void add() {
		int i = 1;
		int b = i + 3;
}

利用javap 反编译后的结果中,没有monitorenter和monitorexit这一对字节码指令

对于同步方法来说,在常量池中多了一个ACC_SYNCHRONIZED标志位,用来标记该方法是否是一个同步方法,JVM会检查该标志位来完成方法的同步:
当方法被调用时,首先会检查该方法的ACC_SYNCHRONIZED标志位是否被设置

  • 如果设置了,表明该方法是一个同步方法,会先去持有Monitor,然后执行方法体。
  • 在方法执行期间,其他线程都无法获取到同一个Monitor。
  • 如果在方法执行期间发生了无法处理的异常,那么在抛出异常时,会自动释放该Monitor。

无论是同步方法还是同步代码块,这两种同步的本质是没有区别的,都是通过Monitor监视器对象来实现的。

ObjectMonitor

在JVM中,Monitor是由ObjectMonitor实现的,
ObjectMonitor整体上分为两部分,一部分是是这个监控对象的基本信息,表示当前锁的实时状态,一部分表示各种情况下需要获取锁的排队信息。如图所示:

具体的工作流程是:

  1. 每个等待锁的线程会被封装成ObjectWaiter对象,当线程需要获取Object Monitor时,将线程封装成ObjectWaiter对象,放入Entry Set集合中。
  2. 当线程获取到ObjectMonitor后,就可以获取到临界资源了,同时ObjectMonitor内部的owner属性指向此线程。每个ObjectMonitor同一时刻只有一个线程进入。
  3. 如果线程获取到了ObjectMonitor之后,在执行过程中调用了wait()或wait(timeout)方法,则当前线程进入到wait Set集合,并释放持有的ObjectMonitor。
  4. 当其他线程进入ObjectMonitor之后,调用notify()或notifyAll(),则wait Set中的线程会被唤醒,重新进入Entry Set去争夺ObjectMonitor
    大致的工作流程就是这样。

synchronized的可重入性

从互斥锁的设计上看,当一个线程试图操作另一个线程持有的锁临界资源时,会进入阻塞状态;
当一个线程再次请求自己持有对象锁的临界资源时,请求就会成功。
在Java中,synchronized是基于原子性的内部锁机制,是可重入的,因此在一个线程调用synchronized方法的同时在其方法体内部调用该对象另一个synchronized方法,是可以的

线程获取到了锁之后,再次请求该锁对象的临界资源,是允许的,这就是synchronized的可重入性。
例如:

class MyRun{
    int i = 0;
    int j = 100;
    public synchronized void add(){
        i ++;
        increase(); // 再次获取该对象锁,直接允许,因为当前线程已经持有了该锁
    }
    
    public synchronized void increase(){
        j --;
    }
}

当线程执行到了add()方法,说明该线程已经拥有了该锁,在add()方法中,再次请求increase()方法,因为该方法的锁已经被当前线程持有了,所以直接允许,操作成功,这就是可重入性。

synchronized的优化

锁的状态共有四种:无锁、偏向锁、轻量级锁、重量级锁。
随着线程的竞争激励程度增加,锁可以从偏向锁升级到轻量级锁,再升级到重量级锁,锁的升级是单向的,只能从低到高升级,不会出现锁的降级
可以看我的这篇文章synchronized锁膨胀、锁升级、锁优化

等待唤醒与synchronized

notify()、notifyAll()和wait()这三个方法,就是实现多线程中的等待唤醒机制,使用这三个方法时,必须处于synchronized代码块或synchronized方法中,否则抛出异常,这是因为调用这几个方法前必须要拿到当前锁对象的监视器对象,也就是notify()、notifyAll()、wait()方法依赖于Monitor对象

注意:与sleep()方法不同的是,wait()方法调用完成后,线程会被暂停,线程将会释放当前持有的Monitor,直到其他线程调用notify()或notifyAll()方法后才能重新竞争锁;而sleep()方法只让线程休眠但不释放锁。

参考资料

深入理解Java并发之synchronized实现原理_synchronized原理_zejian_的博客-CSDN博客

Java并发编程:Synchronized及其实现原理 - liuxiaopeng - 博客园

☆啃碎并发(七):深入分析Synchronized原理 - 简书

JAVA系列教程:Object Monitor与Synchronized关键字_Mary Ling的博客-CSDN博客

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

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

相关文章

mysql 笔记(二)-mysql存储引擎

存储引擎在mysql体系架构中位于第三层, 负责mysql中的数据存储和提取,根据mysql提供的文件访问层抽象接口定制的一种文件访问机制. 使用show engines命令可以查看当前数据库支持的引擎信息. 从截图可看到, mysql 默认的存储引擎是InnoDB,支持事务,行锁,外键,支持分布式事务(…

SSD是否可以提升游戏性能或帧数?

​在购买这种新型硬盘之前,你可能会有些疑问。在这篇文章中,我将解释什么是固态硬盘(SSD),它是否能提升游戏性能,以及如何将你的旧硬盘替换为新的固态硬盘。​ 更换SSD可以让我的游戏运行更流畅吗&#xff…

TB/TM-商品详情

一、接口参数说明: item_get-获得商品详情,点击更多API调试,请移步注册API账号点击获取测试key和secret 公共参数 请求地址: https://api-gw.onebound.cn/taobao/item_get 名称类型必须描述keyString是调用key(点击获取测试key…

ssm+vue医院住院管理系统源码和论文PPT

ssmvue医院住院管理系统源码和论文PPT012 开发工具:idea 数据库mysql5.7(mysql5.7最佳) 数据库链接工具:navcat,小海豚等 开发技术:java ssm tomcat8.5 摘 要 随着时代的发展,医疗设备愈来愈完善,医院也变成人们生…

解密Flink的状态管理:探索流处理框架的数据保留之道,释放流处理的无限潜能!

水善利万物而不争,处众人之所恶,故几于道💦 文章目录 一、什么是状态二、应用场景三、Flink中状态的分类四、算子状态1. 列表状态(List State)2. 广播状态(Broadcast State) 五、键控状态1. Val…

基于Springboot+vue+elementUI+MySQL的学生信息管理系统(一)前端部分

源码在本人博客资源当中,本文为项目代码的详细介绍解释,供于大家学习使用 Vue项目的入口文件:mian.js //vue项目入口文件 //导入vue import Vue from vue //导入根组件app import App from ./App //导入路由文件 import router from ./rout…

Jsoup爬取简单信息

1. 豆瓣图书最受关注 1.1 创建SpringBoot项目或者Maven项目 1.2 引入jsoup <dependency><!-- jsoup HTML parser library https://jsoup.org/ --><groupId>org.jsoup</groupId><artifactId>jsoup</artifactId><version>1.15.3<…

全国区划代码数据筛选重组

你知道的越多&#xff0c;你不知道的越多 点赞再看&#xff0c;养成习惯 如果您有疑问或者见解&#xff0c;欢迎指教&#xff1a; 企鹅&#xff1a;869192208 文章目录 前言引入jar包实现思路代码实现验证 Guava工具类找出两个 Map 集合的差异数据筛选残联区划和全国区划差异组…

宇凡微电热毯方案开发,多档调节带定时

电热毯在1912年发明&#xff0c;到现在已有百年历史。现在的电热毯更有了许多智能化产品&#xff0c;这么多年来拯救了许多怕冷的小伙伴们&#xff0c;在寒冷的冬季靠它续命。宇凡微推出的电热毯方案&#xff0c;电热毯单片机使用54E&#xff0c;实现的功能有档位调节&#xff…

扬起的沙尘如何形成卷云

被气旋吹到空中的沙尘为冰云的形成提供了成核粒子。 卷云是由空气中的冰粒形成的。 卷云是由纯冰粒子组成的高云&#xff0c;主要在8-17 公里高空出现。 这些云通过散射入射的阳光和吸收地球发出的红外辐射&#xff0c;对地球的气候产生重要影响。 在一项最新的研究中&#xf…

保姆级SPSS图文安装教程

1.SPSS安装包下载 链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;rb0n 2.SPSS安装 1.上面压缩包解压后双击解压文件中的setup.exe 2.点击下图绿色框中内容进行安装 3.下一步 4.接受协议&#xff0c;下一步 5.是&#xff0c;下一步 6.接受协议&#xff0c;下一步…

python菱形问题

Python类分为两种&#xff0c;一种叫经典类&#xff0c;一种叫新式类。都支持多继承&#xff0c;但继承顺序不同。 新式类&#xff1a;从object继承来的类。&#xff08;如:class A(object)&#xff09;&#xff0c;采用广度优先搜索的方式继承&#xff08;即先水平搜索&#…

图分类,图机器学习最新进展

图分类&#xff0c;图机器学习最新进展 1.Flat_Pooling TitleVenueTaskCodeDatasetDMLAP: Multi-level attention pooling for graph neural networks: Unifying graph representations with multiple localitiesNeural Networks 20221. Graph ClassificationNonesynthetic, …

Tomcat日志中文乱码

修改安装目录下的日志配置 D:\ProgramFiles\apache-tomcat-9.0.78\conf\logging.properties java.util.logging.ConsoleHandler.encoding GBK

感受RFID服装门店系统的魅力

嘿&#xff0c;亲爱的时尚追随者们&#xff01;今天小编要给你们带来一股时尚新风潮&#xff0c;让你们感受一下什么叫做“RFID服装门店系统”&#xff0c;这个超酷的东西&#xff01; 别着急&#xff0c;先别翻白眼&#xff0c;小编来解释一下RFID是什么玩意儿。它是射频识别…

Android使用Gradle kotlin dsl 优雅配置构建项目

目录 概述1.Gradle Kotlin-DSL配置1.1 在根目录下建立一个buildSrc目录&#xff0c;1.2.新建build.gradle.kts文件并添加Kotlin dsl相关配置 2.Gradle Kotlin DSL 的编写2.1 定义项目的版本号信息2.2.定义Dependencies管理项目中需要使用的库依赖2.3 定义APK的打包脚本构建APK的…

R-Meta分析与【文献计量分析、贝叶斯、机器学习等】多技术融合

Meta分析是针对某一科研问题&#xff0c;根据明确的搜索策略、选择筛选文献标准、采用严格的评价方法&#xff0c;对来源不同的研究成果进行收集、合并及定量统计分析的方法&#xff0c;最早出现于“循证医学”&#xff0c;现已广泛应用于农林生态&#xff0c;资源环境等方面。…

600份国家自然基金申报书--模板

600份国家自然基金申报书--模板 0、引言1、 目录2、网盘链接 ⚠申明&#xff1a; 未经许可&#xff0c;禁止以任何形式转载&#xff0c;若要引用&#xff0c;请标注链接地址。 全文共计3077字&#xff0c;阅读大概需要3分钟 &#x1f308;更多学习内容&#xff0c; 欢迎&#x…

增速骤降2703亿

号外&#xff1a;公众号「刘教链Pro」今日发表《如果牛市停止加仓》。公众号「刘教链内参」今日发表《内参&#xff1a;灰度负溢价继续收窄&#xff0c;微策略跑赢一切》。欢迎点击阅读。 * * * * * * 日前&#xff0c;中国人民银行网站公布了7月份的金融统计数据报告&#xf…

原生信息流广告特点,如何帮APP开发者增加变现收益?

简单来说&#xff1a;原生广告&#xff0c;就是把广告片和账号&#xff0c;一起用消耗推流的买量模式&#xff0c;一同投放出去。 用户看到的广告/内容&#xff0c;与原生视频没有差别——用户可以点头像关注、也可以查看账号历史信息。原生广告本质&#xff0c;是显得真实、原…