关于Java中synchronized的实现原理

并发编程的三个理念

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

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博客

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

时序预测 | Matlab实现基于RF随机森林的电力负荷预测模型

文章目录 效果一览基本介绍模型描述源码设计学习小结参考资料效果一览 基本介绍 时序预测 | Matlab实现基于RF随机森林的电力负荷预测模型 电力负荷预测是指通过对历史电力负荷数据分析,来预测未来某个时间段内的电力负荷需求。这项预测对于电力系统的运行和调度至关重要,可以…

【Echart地图】jQuery+html5基于echarts.js中国地图点击弹出下级城市地图(附完整源码下载)

文章目录 写在前面涉及知识点实现效果1、实现中国地图板块1.1创建dom元素1.2实现地图渲染1.3点击地图进入城市及返回 2、源码分享2.1 百度网盘2.2 123云盘2.3 邮箱留言 总结 写在前面 这篇文章其实我主要是之前留下的一个心结,依稀记得之前做了一个大屏项目的时候&…

【Sklearn】基于决策树算法的数据分类预测(Excel可直接替换数据)

【Sklearn】基于决策树算法的数据分类预测(Excel可直接替换数据) 1.模型原理1.1 模型原理1.2 数学模型 2.模型参数3.文件结构4.Excel数据5.下载地址6.完整代码7.运行结果 1.模型原理 决策树是一种基于树状结构的分类和回归模型,它通过一系列…

C++ QT(一)

目录 初识QtQt 是什么Qt 能做什么Qt/C与QML 如何选择Qt 版本Windows 下安装QtLinux 下安装Qt安装Qt配置Qt Creator 输入中文配置Ubuntu 中文环境配置中文输入法 Qt Creator 简单使用Qt Creator 界面组成Qt Creator 设置 第一个Qt 程序新建一个项目项目文件介绍项目文件*.pro样式…

【网络】传输层——UDP | TCP(协议格式确认应答超时重传连接管理)

🐱作者:一只大喵咪1201 🐱专栏:《网络》 🔥格言:你只管努力,剩下的交给时间! 现在是传输层,在应用层中的报文(报头 有效载荷)就不能被叫做报文了,而是叫做数…

【Sklearn】基于最中心分类器算法的数据分类预测(Excel可直接替换数据)

【Sklearn】基于最中心分类器算法的数据分类预测(Excel可直接替换数据) 1.模型原理2.模型参数3.文件结构4.Excel数据5.下载地址6.完整代码7.运行结果 1.模型原理 最近中心分类器(Nearest Centroid Classifier)也被称为近似最近邻…

若依框架浅浅介绍

由若依官网所给介绍可知 1、文件结构介绍 在ruoyi-admin的pom.xml文件中引入了ruoyi-framework、ruoyi-quartz和ruoyi-generatior模块,在ruoyi-framework的pom.xml文件中引入了ruoyi-system模块。 2、技术栈介绍 前端:Vue、Element UI后端&#xff1a…

xxljob搭建(内网穿透)

调度中心搭建 先从码云或者github上将项目拷贝到本地,选择最新的release分支拷贝下来的xxl-job-admin模块就是调度中心,我们需要做的有两点,第一点将doc/db/tables_xxl_job.sql执行,第二点修改xxl-job-admin的application.proper…

SAP Fiori 将GUI中的自开发报表添加到Fiori 工作台

1. 首先我们在workbench 中开发一个GUI report 这里我们开发的是一个简单的物料清单报表 2. 分配一个事务代码。 注意这里的SAP GUI for HTML 要打上勾 3. 创建语义对象( Create Semantic Object) 事物代码: path: SAP NetWeaver ->…

2. 获取自己CSDN文章列表并按质量分由小到大排序(文章质量分、博客质量分、博文质量分)(阿里云API认证)

文章目录 写在前面步骤打开CSDN质量分页面粘贴查询文章url按F12打开调试工具,点击Network,点击清空按钮点击查询是调了这个接口https://bizapi.csdn.net/trends/api/v1/get-article-score用postman测试调用这个接口(不行,认证不通…

React源码解析18(6)------ 实现useState

摘要 在上一篇文章中,我们已经实现了函数组件。同时可以正常通过render进行渲染。 而通过之前的文章,beginWork和completeWork也已经有了基本的架子。现在我们可以去实现useState了。 实现之前,我们要先修改一下我们的index.js文件&#x…

nodejs+vue+elementui美食网站的设计与实现演示录像2023_0fh04

本次的毕业设计主要就是设计并开发一个美食网站软件。运用当前Google提供的nodejs 框架来实现对美食信息查询功能。当然使用的数据库是mysql。系统主要包括个人信息修改,对餐厅管理、用户管理、餐厅信息管理、菜系分类管理、美食信息管理、美食文化管理、系统管理、…

java内存模型JMM

Java内存模型的主要目标:定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。 主内存:所有的变量都存储在主内存,且线程共享。 工作内存:每条线程私有,保存了…

Linux目录结构(快速了解)

linux目录结构 核心 1.Linux一切皆文件 2.只有一个顶级目录,而windows分C盘、D盘等 目录结构 目录含义(与windows进行比对) Linux含义windows/bin所有用户可用的基本命令存放的位置windows无固定的命令存放目录/bootlinux系统启动的时候需要…

C进阶(1/7)——数据在内存中的存储

目录 前言: 一.数据类型介绍 类型基本归类: 整型家族: 浮点数家族: 构造类型: ​指针类型: 空类型: 二.整型在内存中的存储 1.原码,反码,补码 2.大小端介绍 3.练…

分布式 - 消息队列Kafka:Kafka 消费者消息消费与参数配置

文章目录 1. Kafka 消费者消费消息01. 创建消费者02. 订阅主题03. 轮询拉取数据 2. Kafka 消费者参数配置01. fetch.min.bytes02. fetch.max.wait.ms03. fetch.max.bytes04. max.poll.records05. max.partition.fetch.bytes06. session.timeout.ms 和 heartbeat.interval.ms07.…

2023国赛数学建模思路 - 复盘:光照强度计算的优化模型

文章目录 0 赛题思路1 问题要求2 假设约定3 符号约定4 建立模型5 模型求解6 实现代码 建模资料 0 赛题思路 (赛题出来以后第一时间在CSDN分享) https://blog.csdn.net/dc_sinor?typeblog 1 问题要求 现在已知一个教室长为15米,宽为12米&…

(css)点击前隐藏icon图表 点击后显示

(css)点击前隐藏icon图表 点击后显示 效果 html <liv-for"(item,index) in sessionList":key"index"class"liClass":class"{ active: change2 index }"tabindex"2">...<el-tooltip class"item" effec…

opencv实战项目 手势识别-实现尺寸缩放效果

手势识别系列文章目录 手势识别是一种人机交互技术&#xff0c;通过识别人的手势动作&#xff0c;从而实现对计算机、智能手机、智能电视等设备的操作和控制。 1. opencv实现手部追踪&#xff08;定位手部关键点&#xff09; 2.opencv实战项目 实现手势跟踪并返回位置信息&…

算法与数据结构(二十四)最优子结构原理和 dp 数组遍历方向

注&#xff1a;此文只在个人总结 labuladong 动态规划框架&#xff0c;仅限于学习交流&#xff0c;版权归原作者所有&#xff1b; 本文是两年前发的 动态规划答疑篇open in new window 的修订版&#xff0c;根据我的不断学习总结以及读者的评论反馈&#xff0c;我给扩展了更多…