【多线程】synchronized 原理

1. 写在前面

本章节主要介绍 synchronized 的一些内部优化机制,这些机制存在的目的呢就是让 synchronized 这把锁更高效更好用!


2. 锁升级/锁膨胀

JVM 将 synchronized 锁分为以下四种状态:

无锁,偏向锁,轻量级锁,重量级锁

在 synchronized 进行加锁的时候,首先会进入到偏向锁的状态,偏向锁不是真正的加锁,而是占个位置,有需要再加锁,没有需要就不加锁,这样一来则减少了加锁解锁的开销,一旦在使用过程中,另一个线程也尝试加锁,那么在另一个线程加锁前,持有偏向锁状态的线程会迅速的把偏向锁升级为真正的加锁状态。

如果在使用过程中,没有其他线程尝试加锁,也就是没有出现锁竞争,那么在 synchronized 执行完后,取消偏向锁即可。

当 synchronized 发生锁竞争时,就会从偏向锁升级成轻量级锁,此时 synchronized 相当于是通过自旋的方式来进行加锁的。

升级成轻量级锁后,如果其他线程很快的释放锁,自旋的方式是很划算的,如果迟迟拿不到锁,一直自旋占用 CPU 资源其实并不划算,而 synchronized 并不是无休止的自旋,自旋到一定程度,发现还是获取不到锁,就会再次升级成重量级锁(挂起等待锁)。

在 synchronized 内部的自旋循环中,有一个计数器,记录循环了多少次,循环多久了,达到一定程度就会执行重量级锁的逻辑,如果线程进行了重量级加锁,并且发生了锁竞争,此时未获取到锁的线程就会被放入阻塞队列中,暂时不参与 CPU 调度了,直到锁释放,才有机会被调度到,才有机会获取到锁。

注意:在 JVM 主流实现中,没有锁降级,当前锁只能升级,只要指定的锁对象,已经被升级了,就回不了头了!


3. 锁消除

锁消除是由编译器智能判定的,看当前的代码是否有必要加锁,如果当前的场景不需要加锁,程序猿加了也是白加,编译器就会自动把锁给消除掉。

比如 StringBuffer 很多关键方法都带有 synchronized,但是如果在单线程中使用 StringBuffer,此时加锁与不加锁完全没有任何区别,而且加锁还有更多的开销,于是编译器就会把这些加锁操作给自动取消了,这就是锁消除机制。


4. 锁粗化

这里就涉及到一个术语,锁的粒度。

锁的粒度:synchronized 包含的代码越多,粒度就越粗,包含的代码越少,粒度就越细。

举个例子:


public void test() {synchronized (this) {// 10w 行代码...// ...}
}

这里的写法就相当夸张了,开发中基本不存在,但这样显而易见一个 synchronized 包裹的代码块中有 10w 行代码,这里的粒度是非常粗的,我们要尽量避免这种情况,在通常情况下,锁的粒度越小是越好的。

因为加锁部分的代码是不能并发执行的,粒度越细,能并发的代码就越多了。

但是在有些情况下,锁的粒度真的越细越好吗?其实也不一定,比如:


public void test() {synchronized (this) {// 10 行代码...}// 2 行代码...synchronized (this) {// 10 行代码...}// 2 行代码...synchronized (this) {// 10 行代码...}
}

此时两次相邻加锁之间,间隙非常少,此时还不如用一个 synchronized 包裹起来!

为什么,因为加锁解锁也是有开销的!

这里试想一下,有一天领导给你安排了三个任务,领导要求你做完后打电话进行汇报。

做法1:

每当完成一个任务就打电话给领导汇报一次:

第一次打电话:领导,我任务一完成了

第二次打电话:领导,我任务二完成了

第三次打电话:对不起,您拨打的电话正在通话中,请稍后再拨...

最后领导不耐烦,你被炒鱿鱼了。

做法2:

把三个任务都完成了,一次性跟领导汇:

打电话:领导,我任务一,二,三都完成了!领导:小伙子不错啊!

最后领导满意,你升职加薪。

所以我们要结合代码来适当的调整锁的粒度


5. 常见锁策略相关面试题

5.1 你是如何理解乐观锁和悲观锁的?

乐观锁认为多个线程访问同一个变量冲突的概率不大,所以乐观锁也不会真正的加搜,会直接尝试访问数据,在访问的同时去识别当前数据是否出现访问冲突,也就是引入一个版本号,借助版本号来识别当前的数据访问是否冲突了

悲观锁的实现就是先加锁,他认为多个线程访问同一个变量的冲突率很大,每次都会真正的加锁,比如借助操作系统提供的mutex,只有获取到了锁,才会操作数据,获取不到锁就会阻塞等待

5.2 介绍下读写锁

读写锁就是把读操作和写操作分别进行加锁

  • 读锁和读锁之间不存在互斥

  • 写锁和写锁之间存在互斥

  • 写锁和读锁之间存在互斥

读写锁最主要用在"频繁读,不频繁写"的场景中

5.3 什么是自旋锁,为什么要使用自旋锁策略呢?缺点是什么?

自旋锁如果获取锁失败,就会立即尝试获取锁,无限循环,获取到锁位置,这样的好处是,一旦锁被释放,就能在第一时间发现,也就是能第一时间获取锁,但如果其他线程锁持有的时间太长,就会浪费CPU资源,所以自旋锁更适合在锁持有时间短的场景下使用

5.4 synchronized 是可重入锁吗?

是可重入锁,可重入锁指的是一个线程对同一个对象连续加锁两次,如果没有出现死锁,就是不可重入锁

具体实现是在锁中记录该锁持有的线程身份,以及一个计数器(记录加锁次数),如果发现当前加锁的线程是持有锁的线程,则直接计数自增。


下期预告:【多线程】JUC的常见类

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

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

相关文章

服务器测试之GPU shoc-master测试

精简版指导 lspci | grep -i nvidia lspci -s 4f:00.0 -vvv 适用版本 cuda_11.8.0_520.61.05_linux.run cuda-samples-11.8.tar.gz NVIDIA-Linux-x86_64-525.116.04.run 安装: ./NVIDIA-Linux-x86_64-525.116.04.run 查看是否为一拖八:nvidia-smi topo …

算法通关村第四关——最大栈问题解析

力扣716,设计一个最大栈数据结构,既支持栈操作,又支持查找栈中最大元素。 分析: 在最大栈的问题上,除了实现普通栈拥有的方法pop、push、top外,还需要实现getMax方法来找到当前栈里的最大值。为了在最短事件…

js 正则表达式配合replace进行过滤html字符串遇到的性能问题

问题场景复现: 博主要实现一个邮箱列表,其中列表中的每一封邮件都有一个摘要,但是摘要是要自己从后端提供的content内容区自己过滤掉所有,只留下纯文本内容的前面几行作为摘要。 性能问题 当我测试到一个邮箱,其中的…

【CSS】说说对BFC的理解

目录 一、概念 二、BFC的布局规则 三、设置BFC的常用方式 四、BFC的应用场景 1、解决浮动元素令父元素高度坍塌的问题 2、解决非浮动元素被浮动元素覆盖问题 3、解决外边距垂直方向重合的问题 五、总结 一、概念 我们在页面布局的时候,经常出现以下情况&am…

【java】基础——封装

封装是对类提供的方法实现对隐藏信息的操作和访问,但留出了访问的借口,以提高类的信息安全性;包:package 包名; 必须放在源文件的第一行;一个java源文件只能有一个package语句;包名全英文小写;命…

网络安全进阶学习第十二课——SQL手工注入3(Access数据库)

文章目录 注入流程:1、判断数据库类型2、判断表名3、判断列名4、判断列数1)判断显示位 5、判断数据长度6、爆破数据内容 注入流程: 判断数据库类型 ——> 判断表名 ——> 判断列名 ——> 判断列名长度 ——> 查出数据。 asp的网…

数池塘(四方向,八方向)深搜

题面 题目描述 农夫约翰的农场可以表示成 NM个方格组成的矩形。由于近日的降雨,在约翰农场上的不同地方形成了池塘。每一个方格或者有积水(W)或者没有积水(.)。 农夫约翰打算数出他的农场上共形成了多少池塘。一个池塘…

商用服务机器人公司【Richtech Robotics】申请纳斯达克IPO上市

来源:猛兽财经 作者:猛兽财经 猛兽财经获悉,总部位于美国内华达州拉斯维加斯由华人领导的商用服务机器人公司【Richtech Robotics】近期已向美国证券交易委员会(SEC)提交招股书,申请在纳斯达克IPO上市&am…

Linux的shell脚本常用命令

1、前提 使用shell脚本可以将所要执行的命令行进行汇总,统一执行,制作为脚本工具,简化重复性工作 1.1、常用命令 1.1.1、启动命令 假设我们拥有一个halloWord.sh的脚本,通过cd 命令进入相对应的目录下 ./halloWord.sh1.1.2、…

SpringBoot 依赖管理和自动配置---带你了解什么是版本仲裁

😀前言 本篇博文是关于SpringBoot 依赖管理和自动配置,希望能够帮助到您😊 🏠个人主页:晨犀主页 🧑个人简介:大家好,我是晨犀,希望我的文章可以帮助到大家,您…

linux epoll介绍与代码演示

在Linux系统中,epoll和poll是两种I/O多路复用技术,它们可以让单个线程处理多个I/O事件。 poll是一种比较早的技术,它的工作方式是轮询所有的文件描述符,看哪些文件描述符准备好了I/O操作。这种方式的缺点是,当文件描述符的数量很大时,轮询的开销会很大。 epoll是为了解…

一个Python程序引发的思考

import disdef add(a, b):result a b 2 3return resultdis.dis(add)# dis.opname 是一个列表,它按字节码值的顺序列出了所有的操作名。 print(dis.opname)# dis.opmap 是一个字典,其键是操作名,值是对应的字节码值。 print(dis.opmap)# 列…

关于机器视觉应用开发四大软件薪资分析(华东)

(QQ群有答疑)视觉人机器视觉-海康机器视觉Visionmaster二次开发课程-零基础小白快速上手VM开发系列课程 现场市场总和占用率我们称作四大软件分别是 1.Visionmaster 2.Halcon 3.Visionpro 4.Opencv 如果大家要详细了解四大软件的应用以及详细情况&a…

Vue——webpack

webpack 一、Install1.全局安装2.局部安装 二、总结1.打包2.定义脚本3.配置文件定义(webpack.config.js)4.项目重新加载依赖5.webpack打包Css6.style-loader 一、Install 1.全局安装 npm install webpack webpack-cli -g2.局部安装 以项目为单位,一个项…

python中的matplotlib画饼图(数据分析与可视化)

直接开始 1、先安装pandas和matplotlib pip install pandas pip install matplotlib2、然后在py文件中导入 import pandas as pd import matplotlib.pyplot as plt3、然后直接写代码 import pandas as pd import matplotlib.pyplot as pltpd.set_option("max_columns&…

2023牛客暑期多校训练营7-c-Beautiful Sequence

思路: ,则有,也就是说只要知道A1就可以求任意A。由于A是升序排列,所以对于任意,二进制所包含1的最高位第k位来说,表明与第k位相反,要大一些,所以它的第k位为1,的第k位为…

c++使用条件变量实现生产消费问题(跨平台)

1. 生产者线程 思路:队列满了的情况下, 触发条件变量wait, 等待消费线程消费后唤醒继续生产. void ProducerThreadFunc() {while(1) { while(/* 容器已满 */) { /* 线程等待, 直到消费者消费后唤醒继续执行 */ }/* 生产动作 */ } }2. 消…

【Linux命令详解 | cp命令】Linux系统中用于复制文件或目录的命令

文章标题 简介参数列表二,使用介绍1. 复制单个文件2. 复制多个文件3. 复制目录4. 保留文件属性5. 创建链接6. 强制覆盖7. 显示复制进度8. 创建备份9. 只有当源文件比目标文件新时才复制10. 复制链接文件 总结 简介 cp命令在Linux系统中用于复制文件或目录。其功能强…

uniapp根据高度表格合并

没有发现比较友好的能够合并表格单元格插件就自己简单写了一个,暂时格式比较固定 一、效果如下 二、UI视图+逻辑代码 <template><view><uni-card :is-shadow="false" is-full

出现Error: Cannot find module ‘compression-webpack-plugin‘错误

错误&#xff1a; 解决&#xff1a;npm install --save-dev compression-webpack-plugin1.1.12 版本问题