当前位置: 首页 > news >正文

并发编程——synchronized优化原理

如果有兴趣了解更多相关内容,欢迎来我的个人网站看看:耶瞳空间

一:基本概念

使用synchronized实现线程同步,即加锁,实现的是悲观锁。加锁可以使一段代码在同一时间只有一个线程可以访问,在增加安全性的同时,牺牲掉的是程序的执行性能。

为了在一定程度上减少获得锁和释放锁带来的性能消耗,在jdk6之后便引入了“偏向锁”和“轻量级锁”,所以Java中的锁总共有4种锁状态,级别由低到高依次为:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。它会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。

锁竞争:如果多个线程轮流获取一个锁,但是每次获取锁的时候都很顺利,没有发生阻塞,那么就不存在锁竞争。只有当某线程尝试获取锁的时候,发现该锁已经被占用,只能等待其释放,这才发生了锁竞争。

在这里插入图片描述

二:无锁

无锁是指没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功。

无锁的特点是修改操作会在循环内进行,线程会不断的尝试修改共享资源。如果没有冲突就修改成功并退出,否则就会继续循环尝试。如果有多个线程修改同一个值,必定会有一个线程能修改成功,而其他修改失败的线程会不断重试直到修改成功。

三:偏向锁

偏向锁,顾名思义,它会偏向于第一个访问锁的线程。如果在运行过程中,同步锁只有一个线程访问,不存在多线程争用的情况,则线程是不需要触发同步的,这种情况下,就会给线程加一个偏向锁。线程第二次到达同步代码块时,会判断此时持有锁的线程是否就是自己,如果是则正常往下执行。由于之前没有释放锁,这里也就不需要重新加锁。如果自始至终使用锁的线程只有一个,很明显偏向锁几乎没有额外开销,性能极高。

偏向锁的获取流程:

  • 查看Mark Word中偏向锁的标识以及锁标志位,若是否偏向锁为1且锁标志位为01,则该锁为可偏向状态。
  • 若为可偏向状态,则测试Mark Word中的线程ID是否与当前线程相同,若相同,则直接执行同步代码,否则进入下一步。
  • 当前线程通过CAS操作竞争锁,若竞争成功,则将Mark Word中线程ID设置为当前线程ID,然后执行同步代码,若竞争失败,进入下一步。
  • 当前线程通过CAS竞争锁失败的情况下,说明有竞争。当到达全局安全点时之前获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码。

如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会释放它的偏向锁,将锁恢复到标准的轻量级锁。释放偏向锁的时候会导致STW(stop the word)操作。

偏向锁的释放流程:偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁状态的线程才会释放锁,线程不会主动去释放偏向锁。偏向锁的撤销需要等待全局安全点(即没有字节码正在执行),它会暂停拥有偏向锁的线程,撤销后偏向锁恢复到未锁定状态或轻量级锁状态。

四:轻量级锁

在轻量级锁状态下继续锁竞争,没有抢到锁的线程将自旋,即不停地循环判断锁是否能够被成功获取。长时间的自旋操作是非常消耗资源的,一个线程持有锁,其他线程就只能在原地空耗CPU,执行不了任何有效的任务,这种现象叫做忙等(busy-waiting)。如果锁竞争情况严重,某个达到最大自旋次数的线程,会将轻量级锁升级为重量级锁。

锁自旋:如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。

轻量级锁的加锁过程:

  1. 当线程执行代码进入同步块时,若Mark Word为无锁状态,虚拟机先在当前线程的栈帧中建立一个名为Lock Record的空间,用于存储当前对象的Mark Word的拷贝,官方称之为“Dispalced Mark Word”。
  2. 复制对象头中的Mark Word到锁记录中。
  3. 复制成功后,虚拟机将用CAS操作将对象的Mark Word更新为执行Lock Record的指针,并将Lock Record里的owner指针指向对象的Mark Word。如果更新成功,则执行4,否则执行5。
  4. 如果更新成功,则这个线程拥有了这个锁,并将锁标志设为00,表示处于轻量级锁状态。
  5. 如果更新失败,虚拟机会检查对象的Mark Word是否指向当前线程的栈帧,如果是则说明当前线程已经拥有这个锁,可进入执行同步代码。否则说明多个线程竞争,轻量级锁就会膨胀为重量级锁,Mark Word中存储重量级锁(互斥锁)的指针,后面等待锁的线程也要进入阻塞状态。

五:重量级锁

当线程尝试获取锁时,发现被占用的锁是重量级锁,则直接将自己挂起,等待将来被唤醒。在JDK1.6之前,synchronized会直接加重量级锁,很明显现在得到了很好的优化。

重量级锁的特点:其他线程试图获取锁时,都会被阻塞,只有持有锁的线程释放锁之后才会唤醒这些线程。

六:总结

优点缺点适用场景
偏向锁加锁和解锁不需要额外的消耗,和执行非同步方法比仅存在纳秒级的差距如果线程间存在锁竞争,会带来额外的锁撤销的消耗适用于只有一个线程访问同步块场景
轻量级锁竞争的线程不会阻塞,提高了程序的响应速度竞争的线程如果始终得不到锁会使用自旋,消耗CPU追求响应时间,同步块执行速度非常快
重量级锁线程竞争不使用自旋,不会消耗CPU线程阻塞,响应时间缓慢追求吞吐量,同步块执行速度较长

在这里插入图片描述

相关文章:

并发编程——synchronized优化原理

如果有兴趣了解更多相关内容,欢迎来我的个人网站看看:耶瞳空间 一:基本概念 使用synchronized实现线程同步,即加锁,实现的是悲观锁。加锁可以使一段代码在同一时间只有一个线程可以访问,在增加安全性的同…...

LeetCode 剑指 Offer II 083. 没有重复元素集合的全排列

给定一个不含重复数字的整数数组 nums &#xff0c;返回其 所有可能的全排列 。可以 按任意顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff1a;[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]] 1 < nums.length < 6 -10 < nu…...

JSONObject与JSONArray使用区别

目录 1.使用的场景区别 2. 使用方法区别 3.获取方式不同 4. 解析JSON字符串 5.总结 1.使用的场景区别 想通过键值对的形式获取数据&#xff0c;使用JSONObject。如果后台查询的是某个bean的list集合向前端页面传递&#xff0c;使用JSONArray。 2. 使用方法区别 创建方法不…...

经典C程序例程:通过进程ID得到文件名

通过进程ID得到文件名 #include <stdio.h> #include <windows.h> #include <tlhelp32.h> #include <tchar.h>BOOL EnablePrivilege(HANDLE hToken,LPCSTR szPrivName); void DispProcess(void); void DispPrsFile(void);// typedef BOOL (_stdcall *E…...

【Java】Spring MVC程序开发

文章目录Spring MVC程序开发1. 什么是Spring MVC&#xff1f;1.1 MVC定义1.2 MVC 和 Spring MVC 的关系2. 为什么学习Spring MVC&#xff1f;3. 怎么学习Spring MVC&#xff1f;3.1 Spring MVC的创建和连接3.1.1 创建Spring MVC项目3.1.2 RequestMapping 注解介绍3.1.3 Request…...

leetcode题解-704. 二分查找

给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xff0c;写一个函数搜索 nums 中的 target&#xff0c;如果目标值存在返回下标&#xff0c;否则返回 -1。 示例 1: 输入: nums [-1,0,3,5,9,12], target 9 输出: 4 解释: 9 出现…...

2.2 C语言程序的错误条件

在C语言程序中,条件语句决定程序的执行路径,因此条件表达式是程序的关键。 应用最经典的程序,除法的减法实现程序,解释条件表达式的重要性。x=y*q+r,x是被除数,y是除数,q是商,r是余数。 程序的方法, x=(r-y)+y*(1+q)。 main(){ /*错误条件的程序*/ r:=x; q:=0; whil…...

laravel 邮件发送

配置 Laravel 的邮件服务可以通过 config/mail.php 配置文件进行配置。 邮件中的每一项都在配置文件中有单独的配置项&#xff0c;甚至是独有的「传输方式」&#xff0c;允许你的应用使用不同的邮件服务发送邮件 mailers > [smtp > [transport > smtp,host > env(M…...

高性能 Jsonpath 框架,Snack3 3.2.57 发布

Snack3&#xff0c;一个高性能的 JsonPath 框架 借鉴了 Javascript 所有变量由 var 申明&#xff0c;及 Xml dom 一切都是 Node 的设计。其下一切数据都以ONode表示&#xff0c;ONode也即 One node 之意&#xff0c;代表任何类型&#xff0c;也可以转换为任何类型。 强调文档…...

Android---进程间通信机制3

1 服务如何注册到 SM 中 getIServiceManager().addService(name, service, false); getIServiceManger --- new ServiceManagerProxy(new BinderProxy()) BinderInternal.getContextObject --- 返回 BinderProxy 对象 ProcessState::self()->getContextObject: 创建一个 BpB…...

Python实战,爬取金融期货数据

大家好&#xff0c;我是毕加锁。 今天给大家带来的是 Python实战&#xff0c;爬取金融期货数据 文末送书&#xff01; 文末送书&#xff01; 文末送书&#xff01; 任务简介 首先&#xff0c;客户原需求是获取https://hq.smm.cn/copper网站上的价格数据(注&#xff1a;获取的是…...

Allegro如何导入第三方网表操作指导

Allegro如何导入第三方网表操作指导 在用Allegro做PCB设计的时候,除了支持第一方网表的导入,同样也是可以导入第三方网表的,第三方网表如下图 如何导入,具体操作如下 点击Setup点击User Preference...

高码率QPSK调制解调方案(FPGA实现篇)

在前面的章节中,已经讲过QPSK调制的方案和Matlab算法仿真,在本篇中,主要讲解基于FPGA的高速QPSK调制的实现。根据前面提到的技术指标,本系统传输的数据速率为500Mbps,中频为720MHz,因此,传统的串行QPSK调制已经不合适在FPGA中实现,需采用全数字的并行方式进行调制,具体…...

Elasticsearch的RESTful Api使用

Elasticsearch的RESTful Api使用 文章目录Elasticsearch的RESTful Api使用查询集群健康情况查看所有索引其他的_cat命令创建索引删除索引修改索引查看索引创建文档批量操作文档删除文档查询文档全量更新文档局部更新文档索引的搜索分词分析分数说明查询类型分析查询集群健康情况…...

软著申请需要注意的

一、文档格式 &#xff08;1&#xff09;程序源代码和说明文档&#xff0c;源码前后30页&#xff0c;文档前后30页。 &#xff08;2&#xff09;软件源代码和说明书的页眉必须标明软件名称、版本号和页码&#xff0c;应当与申请表中相应内容完全一致 &#xff08;3&#xff09…...

SpringBoot入门 - 添加Logback日志

SpringBoot开发中如何选用日志框架呢&#xff1f; 出于性能等原因&#xff0c;Logback 目前是springboot应用日志的标配&#xff1b; 当然有时候在生产环境中也会考虑和三方中间件采用统一处理方式。日志框架的基础在学习这块时需要一些日志框架的发展和基础&#xff0c;同时了…...

社会实践报告

中文摘要: 注重素质教育的今天&#xff0c;社会实践活动一直被视为高校培养德、智、体、美、劳全面发展的跨 世纪优秀人才的重要途径。团期社会实践活动是学校教育向课堂外的一种延伸&#xff0c;也是推进素质教育进程的重 手段。它有助于当代大学生接触社会&#xff0c;了解社…...

LeetCode 460. LFU 缓存 -- 哈希查询+双向链表

LFU 缓存 困难 634 相关企业 请你为 最不经常使用&#xff08;LFU&#xff09;缓存算法设计并实现数据结构。 实现 LFUCache 类&#xff1a; LFUCache(int capacity) - 用数据结构的容量 capacity 初始化对象 int get(int key) - 如果键 key 存在于缓存中&#xff0c;则获取键…...

Dubbo 源码分析 – SPI 机制

1.简介 SPI 全称为 Service Provider Interface&#xff0c;是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中&#xff0c;并由服务加载器读取配置文件&#xff0c;加载实现类。这样可以在运行时&#xff0c;动态为接口 加载实现类。正因此特性&#xff0…...

JDBC概述二(JDBC编程+案例展示)

一&#xff08;JDBC的编程步骤&#xff09; 1.加载数据库驱动 加载数据库驱动通常使用class类的静态方法forName&#xff08;&#xff09;来实现&#xff0c;具体实现方式如下&#xff1a; Class.forName&#xff08;“DriverName”&#xff09;&#xff0c;DriverName就是数…...

广度和深度优先搜索解析与示例代码

一,什么是搜索算法 算法是基于特定数据结构之上的,深度优先搜索算法和广度优先搜索算法都是基于“图”这种数据结构的。 树是图的一种特例(连通无环的图就是树)。 图上的搜索算法,最直接的理解就是,在图中找出从一个顶点出发,到另一个顶点的路径。具体方法有很多,两种…...

基于SLIC超像素的归一化分割算法

论文&#xff1a;基于SLIC超像素的归一化分割方法研究 归一化分割的缺点&#xff1a;单独使用时无法区分很接近的图像区域&#xff0c;实时性也差。 区域接近问题&#xff1a;描述图像间相互关系的权重函数的取值&#xff0c;体现图像间的信息特征&#xff0c;影响分割效果。…...

C语言刷题(4)——“C”

各位CSDN的uu们你们好呀&#xff0c;今天小雅兰的内容又到了我们的复习啦&#xff0c;那么还是刷题噢&#xff0c;话不多说&#xff0c;让我们进入C语言的世界吧 BC55 简单计算器 BC56 线段图案 BC57 正方形图案 BC58 直角三角形图案 BC59 翻转直角三角形图案 BC60 带空格…...

带你看懂RuoYi动态数据源切换

文章目录数据源是什么一、spring中是如何处理各种数据源的&#xff1f;1.开搞springboot2.创建一个测试类二、有了如上的理论,那么想想动态切换数据源吧参考若依的动态数据源配置总结数据源是什么 数据源,对于java来说,就是可用的数据库,那么我平时开发的springboot springclo…...

家有女儿必看:盲目的和青春期女儿较劲,不如掌握4个沟通技巧

导读&#xff1a;家有女儿必看&#xff1a;盲目的和青春期女儿较劲&#xff0c;不如掌握4个沟通技巧 各位点开这篇文章的朋友们&#xff0c;想必都是很高的颜值吧&#xff0c;我们真的是很有缘哦&#xff0c;小编每天都会给大家带来不一样的育儿资讯&#xff0c;如果对小编的文…...

【VC 7/8】vCenter Server 基于文件的备份和还原Ⅰ——基于文件的备份和还原的注意事项和限制

目录1.1 协议1.2 还原后配置说明1.3 Storage DRS1.4 分布式电源管理1.5 分布式虚拟交换机1.6 内容库1.7 虚拟机生命周期操作1.8 vSphere High Availability1.9 基于存储策略的管理1.10 其它注意事项虚拟存储区域网络修补关联博文[图片来源]&#xff1a;https://www.vmignite.co…...

【ROS学习笔记10】ROS中配置自定义Cpp头文件和导入自定义Python库

【ROS学习笔记10】ROS中配置自定义Cpp头文件和导入自定义Python库 文章目录【ROS学习笔记10】ROS中配置自定义Cpp头文件和导入自定义Python库一、ROS中的头文件和源文件1.1 自定义头文件调用1.2 自定义源文件调用二、Python模块的导入Reference写在前面&#xff0c;本系列笔记参…...

svn 分支(branch)和标签(tag)管理

版本控制的一大功能是可以隔离变化在某个开发线上&#xff0c;这个开发线就是分支&#xff08;branch&#xff09;。分支通常用于开发新功能&#xff0c;而不会影响主干的开发。也就是说分支上的代码的编译错误、bug不会对主干&#xff08;trunk&#xff09;产生影响。然后等分…...

@Transactional详解

一、事务的概念 百度百科&#xff1a; 事务&#xff08;Transaction&#xff09;&#xff0c;一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执 行单元(unit)。事务通常由高级数据库操纵语言或编程语言&#xff08;如SQL&#x…...

机器学习:Transformer

Transformer sequence-to-sequence(seq2seq) 很大语音没有文本&#xff0c;7000种中超半数没有文字。 遇到的问题&#xff1a; 遇到问题时候可以先不管它&#xff0c;先出一个baseline看看效果&#xff0c;后续再进行提升。 tts&#xff1a; 文本转语音&#xff0c;语音合成…...