【JavaEE】锁的策略

news2024/11/17 3:48:56

作者主页:paper jie_博客

本文作者:大家好,我是paper jie,感谢你阅读本文,欢迎一建三连哦。

本文于《JavaEE》专栏,本专栏是针对于大学生,编程小白精心打造的。笔者用重金(时间和精力)打造,将MySQL基础知识一网打尽,希望可以帮到读者们哦。

其他专栏:《MySQL》《C语言》《javaSE》《数据结构》等

内容分享:本期将会分享锁的策略知识.这也是面试题常考的问题.

目录

乐观锁与悲观锁

轻量级锁与重量级锁

自旋锁与挂起等待锁

普通互斥锁和读写锁

为什么要引入读写锁

公平锁与非公平锁

可重入锁和不可重入锁

synchronized锁与操作系统自带锁对比

synchronized的优化策略

锁的升级

偏向锁阶段(没有其他锁来竞争)

轻量级锁阶段(有锁竞争,但是不多)

重量级锁阶段(有锁竞争,且很多)

锁消除

锁粗化

相关面试题

你是什么理解乐观锁和悲观锁的,具体怎么实现?

介绍一下读写锁

synchronized是可重入锁吗

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


乐观锁与悲观锁

乐观锁就是在加锁前,预估发生锁冲突的概率不大,在进行加锁的时候做的工作不多.这样加锁的速度就会比较快,但是会更容易消耗CPU资源.

悲观锁就是在加锁前,预估发生锁冲突的概率比较大.在进行加锁的时候做的工作就比较多.这样的加锁速度就会比较慢,但这个过程发生问题的几率就不大,还可以更节省CPU资源.

举个栗子:

这就好比两个同学去问问题.A同学认为老师有时间就直接跑过去了,而B同学会先给老师打个电话确认一下老师有没有空再去.A同学就是乐观锁,B同学就是悲观锁. 显而易见,A同学就容易遇到老师很忙的情况,而B同学就可以避免.但老师要是不忙,A同学的速度就会更快.

轻量级锁与重量级锁

轻量级锁就是加锁的开销小,加锁速度快.因为轻量级锁不怎么到涉及线程的调度,所以开销就比较小.

而重量级锁就是加锁的开销比较大,加锁速度慢.因为重量级锁涉及到大量的线程调度,遇到锁竞争就需要将线程调度出CPU,等待解锁再由系统随机唤醒一个等待线程,所所以开销会比较慢.

这里可以理解为轻量级锁就是乐观锁,重量级锁就是悲观锁.而前者是站在结果发生后的角度来评价,后者是站在发生前的角度来预估.

自旋锁与挂起等待锁

自旋锁是轻量级锁的经典实现,自旋锁也就是乐观锁. 它就是在加锁的时候搭配一个while循环,加锁成功就退出循环,要是没有成功发生堵塞,也不会退出CPU,而是通过while循环不断尝试获取到锁. 这种循环就叫做自旋.一旦当其他线程释放锁后,它就可以第一时间拿到锁了. 但是自旋锁只能在锁竞争不大的情况下使用,不然就会白白自旋,浪费CPU资源.

挂起等待锁是重量级锁的经典实现.挂起等待锁也就是悲观锁. 它就是在尝试加锁失败后不会再次尝试获取锁.而是调度出CPU,等待其他线程释放锁后,由系统来随机唤醒一个等待的线程.因为它在等待的过程中会有内核调度器介入,会有大量的线程调度,获取锁的时间就会比较慢.挂起等待锁的适用场景是在锁冲突比较严重的情况下.

而在我们Java中,Synchronized这个锁是一个乐观锁/悲观锁自适应,轻量级锁/重量级锁自适应,自旋/挂起等待锁自适应.它可以根据不同的场景来做切换.

普通互斥锁和读写锁

我们Java的synchronized就是普通互斥锁,不管是读还是写都是进行加锁.而读写锁会有两种情况: 1.加读锁 2. 加写锁. 读写锁就是一个线程读时,另一个线程只能读不能写. 一个线程写时,另一个线程不能读也不能写. 

1 读锁和读锁之间不会发生堵塞.

2 读锁和写锁之间会发生堵塞.

3 写锁和写锁之间会发生堵塞. 

为什么要引入读写锁

因为多线线程进行读操作,它本身就是线程安全的,但是对于普通互斥锁来说,两个线程读,也会发生锁竞争,堵塞,这就会造成一定的性能损失. 但是对读操作不加锁的话,就怕一个线程读,一个线程写,导致读到的内容不完整.

基于这个情况下就引入了读写锁,它就可以解决对于读操作不加锁的问题.直接将读引发的锁竞争的开销给节省下来了,让性能会有很大的提高. 我们在实际开发中读操作是非常多的,使用读写锁对性能就有明显的提升了.

公平锁与非公平锁

公平锁就是遵循先来后到. 假设A锁先进行锁等待,B锁后进行锁等待.等到其他线程将锁释放后,就是A先获取到锁.

非公平锁就是不管你是先进行等待还是后进行等待.等到其他线程释放锁后.大家都可以竞争这把锁.系统的调度是随机的,下一个唤醒谁都不确定.

站在操作系统的角度上来看待锁,就是不公平锁.因为操作系统调度线程是随机的.下一个将谁调度进来,唤醒谁也不确定.我们Java中的synchronized也是不公平锁.而公平锁就需要引入额外的数据结构.

可重入锁和不可重入锁

可重入锁就是对于一个锁对象来说, 同一个线程可以多次获取到这把锁,不会发生死锁.

不可重入锁就是对于一个锁对象来锁,同一个线程不可以多次获取到这个把锁,不然会发生死锁.

我们的synchronized就是可重入锁,它的内部有两个重要的属性, 一个是用来记录当前持有锁的线程.一个是计数器,一次加锁就+1,一次解锁就-1.当变成0就算彻底解锁成功.

synchronized锁与操作系统自带锁对比

synchronized是:

乐观锁/悲观锁自适应

轻量/重量锁自适应

自旋/挂起等待锁自适应

不公平锁

普通互斥锁

系统自带锁是:

悲观锁

重量级锁

挂起等待锁

不公平锁

普通互斥锁

synchronized的优化策略

锁的升级

synchronized内部会有一个升级阶段.当一个线程执行到synchronized这里时,所对象还没被加锁的时候,就会经历: 1.偏向锁阶段 -> 2.轻量级锁阶段 -> 3.重量级锁阶段

偏向锁阶段(没有其他锁来竞争)

就是在这个需要加锁的线程上做一个轻量的标记,并不会真正的去进行加锁.只有等到其他的线程也要对这个锁进行加锁时.它才会在其他线程获取到锁之前将锁获取过来.这样就从偏向锁升级到了轻量级锁.

这里的核心思想就是懒汉模式.不能加锁就不加锁,迫不得已的情况下再进行加锁.这样就可以在没有其他线程需要加锁的情况下省去加锁的开销.

轻量级锁阶段(有锁竞争,但是不多)

这里的轻量级锁实现的就是自旋锁,这里的优点就是可以很快获取到锁,但是会更消耗CPU资源. synchronized内部会记录当前有多少个线程在竞争这把锁,当超过一个量后,轻量级锁就会升级到重量级锁.

重量级锁阶段(有锁竞争,且很多)

这里的重量级锁实现的就是挂起等待锁,遇到锁冲突后就直接挂起等待,也就是调度出CPU,当释放锁后,让系统随机唤醒一个来获取锁.

锁消除

这里也是编译器优化的一种方式.当发现这个代码不需要加锁时,就会将锁消除掉.

锁粗化

锁粗化就是将多个细粒度的锁合并成一个粗粒度的锁. 这里我们可以理解完synchronized{}里的代码越少粒度就越细.大多数情况下是希望粒度越细越容易并发执行代码,但有的情况下还是希望粒度粗一点.

比如:这里的加锁操作太多就会导致锁竞争的开销过大,每次加锁可能都会堵塞.这时将细粒度的锁变成粗粒度的锁就节省了锁堵塞的开销,也会提高效率.

相关面试题

你是什么理解乐观锁和悲观锁的,具体怎么实现?

乐观锁就是在加锁前认为发生锁冲突的几率不大,在加锁的时候做的工作就不多,就不会真的加锁,而是直接尝试访问数据.在访问数据的同时便辨别当前数据是不是出现访问异常.

悲观锁就是在加锁前认为发生锁冲突的概率比较大,在进行加锁的时候做的工作就比较多,每次访问变量前都会去真正的进行加锁.

悲观锁的实现就是先加锁,获取到锁再来操作数据,获取不到锁就等待.乐观锁的实现可以引入一个版本号,借助版本号识别出当前的数据访问是不是有冲突.

介绍一下读写锁

读写锁就是把读操作和写操作分别进行加锁. 读锁与读锁之间不会发生堵塞. 读锁与写锁之间会发生堵塞.写锁和写锁之间也会发生堵塞. 这就是一个线程读的时候,另一个线程只能读不能写.而一个线程写的时候,另一个线程不能读也不能写. 一般使用读写锁的都是在读非常频繁,但写不频繁的场景下.

synchronized是可重入锁吗

synchronized是可重入锁.可重入锁是指在一个线程内,可以对同一个锁进行多次加锁,不会产生死锁.而synchronized可以重入是因为它内部有两个重要的属性,一个是记录持有这个锁的线程身份,另一个是计数器(记录加锁的次数). 如果发现当前加锁的线程就是持有锁的线程,计数器就会++.

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

自旋锁就是当一个线程发生锁竞争后,它不会挂起等待,而是通过while来不断循环来尝试再次获取到锁.一但其他线程释放锁后它就可以立刻获取到锁. 

因为使用自旋锁在第一次获取失败后第二次获取尝试会很快来到,这样就可以快速获取到锁.

优点就是获取锁的速度快,更高效,在锁持有时间比较短且锁竞争小的场景下非常有用,却点就是如果锁竞争多且锁持有时间长的场景下就没有用了且非常浪费CPU资源,因为它一直在不断循环,做的都是无用功.


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

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

相关文章

VG3225EFN压控晶体振荡器(VCXO)

5G脞2020年开始,商业服务正在全球范围内快速部署。5G通信网络需要保持高速率和可靠性,这2两者都需要低噪声,使用高频基模晶体振荡器(高达50MHz),该晶体振荡器可以提供低相位噪声参考时钟,从而降…

EPICS asynPortDriver使用示例

在文本中,将展示如何将EPICS asyn模块和其他库联用,从而实现对arm单板机上GPIO口的控制。 在本例中使用到的硬件是: 在程序中需要厂家提供的wringPi库,才能通过C语言库函数调用实现对其GPIO的控制。 以下是这个单板机GPIO的管脚…

Linux Shell——(脚本参数传递)

脚本参数传递 一、参数传值二、脚本文件中特殊的变量 总结 最近学习了shell脚本,记录一下shell脚本参数传递相关语法 一、参数传值 执行脚本的时候,可以向脚本传递参数,脚本内获取参数的格式为$n n位置从1开始,$0 是脚本的文件名…

1.6 实战:Postman请求Get接口-获取用于登录的图形验证码

上一小节我们学习了Postman的布局,对Postman有了一个整体的认知,本小节我们就来实操一下Get接口。 我们打开Postman,点击我们之前创建的请求”获取登录页验证码“。我们在地址栏里填入获取登录页验证码的接口地址。怎么查看这个接口地址呢?我们打开校园二手交易系统,打开…

生成式AI的力量,释放RPA的无限潜能

回首即将过去的2023年,互联网行业似乎始终处在各种新概念的热潮激荡之中。其中,最引人注目的话题无疑是AI科技。自人工智能技术实现大规模突破以来,我们见证了一系列生成式AI的涌现。从ChatGPT到百度文心一言,它们纷纷登场&#x…

Python - 网络传输数据格式【字节流】传输优点及数据格式【字符,字典,字节,二进制,十六进制】的转换

一. 前言 在网络传输数据时,数据本质上是以二进制形式进行传输的。无论是传输字节还是传输二进制数据,最终都会转化为二进制进行传输。 所以,从传输速度的角度来看,无论是传输字节还是传输二进制数据,实际上是相同的…

OpenHarmony关于修改系统横屏导致启动视频显示不全问题解决

前言 OpenHarmony源码版本:4.0release 开发板:DAYU / rk3568 前段时间写的设置OpenHarmony启动视频,在竖屏状态下是正常的,但是横屏状态下显示不全。 链接直达:OpenHarmony 设备启动Logo和启动视频替换指南-CSDN博…

.net 洋葱模型

洋葱架构 内层部分比外层更抽象(内层接口,外层实现)。外层的代码只能调用内层的代码,内层的代码可以通过依赖注入的形式来间接调用外层的代码 简单的例子,引用依赖图 demo 接口类库 EmailInfo using System; using System.Collections.…

基于ssm一中体育馆管理系统的设计与实现论文

镇赉县一中体育馆管理系统的设计与实现 摘要 随着信息互联网购物的飞速发展,一般有能力的机构管理系统。本文介绍了镇赉县一中体育馆管理系统的开发全过程。通过分析企业对于镇赉县一中体育馆管理系统的需求,创建了一个计算机管理镇赉县一中体育馆管理…

瞳孔检测眼动追踪python实现(基于dlib)

效果展示: 原图:(图片来自 b站up 借我300去洗牙) dlib实现的特征点检测 瞳孔检测结果 完整代码: # encoding:utf-8import dlib import numpy as np import cv2def rect_to_bb(rect): # 获得人脸矩形的坐标信息x …

鸿蒙实操【ArkTS语言的运用】

ArkTS基础知识 使用声明式语法和组件化基础知识,搭建一个可刷新的排行榜页面。在排行榜页面中,使用循环渲染控制语法来实现列表数据渲染,使用Builder创建排行列表布局内容,使用装饰器State、Prop、Link来管理组件状态。最后我们点…

Linux Conda 安装 Jupyter

在Linux服务器Conda环境上安装Jupyter过程中遇到了无数的报错,特此记录。 目录 步骤一:安装Anaconda3 步骤二:配置Conda源 步骤三:安装Jupyter 安装报错:simplejson.errors.JSONDecodeError 安装报错:…

循环神经网络-1

目录 1 数据集构建 1.1 数据集的构建函数 1.2 加载数据并进行数据划分 1.3 构造Dataset类 2 模型构建 2.1 嵌入层 2.2 SRN层 2.3 线性层 2.4 模型汇总 3 模型训练 3.1 训练指定长度的数字预测模型 3.2 多组训练 3.3 损失曲线展示 4 模型评价 总结 参考文献 循环神经网络&…

VC++项目的32位、64位的配置和链接问题

新建一个项目,默认是x86配置; 添加包含目录、库目录,之后可以编译通过; 但是链接会出错,因为链接的dll是64位; 把项目配置改为x64; 需要把包含目录和库目录针对x64重新添加,否则会…

获取CAD图元名及图元信息(circle为例,用于选择集,对应dxf组码)

在CAD编程中往往需要用选择集,我们往往不知道相应图元对应的名称具体名字。比如我想选择所有的圆,ftype0,fdata应该是什么呢?是circle,acdbcircle,还是acadcircle? circle是一个对象,circle的vba类名为Ac…

【CSS】用 CSS 写一个渐变色边框的输入框

Using_CSS_gradients MDN 多渐变色输入框&#xff0c;群友问了下&#xff0c;就试着写了下&#xff0c;看了看 css 渐变色 MDN 文档&#xff0c;其实很简单&#xff0c;代码记录下&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta ch…

【unity小技巧】两种办法解决FPS游戏枪或者人物穿墙穿模问题

文章目录 前言第一种解决思路第二种方法总结感谢完结 前言 当我们开发FPS游戏时&#xff08;其实3d游戏基本都会遇到这样的问题&#xff09;&#xff0c;如果我们不做处理&#xff0c;肯定会出现人物或者枪的穿墙穿模问题&#xff0c;这是是一个常见的挑战。 这种问题会破坏…

Flink窗口的概念和分类

窗口的概念 Flink是一种流式计算引擎&#xff0c;主要是来处理无界数据流的&#xff0c;数据源源不断、无穷无尽。想要更加方便高效地处理无界流&#xff0c;一种方式就是将无限数据切割成有限的“数据块”进行处理&#xff0c;这就是所谓的“窗口”&#xff08;Window&#x…

指针相关知识(进阶)

前面的入门中已经介绍了指针的基础知识&#xff0c;接下来&#xff0c;让我们继续学习吧&#xff01; 一. 字符指针变量 char* 一般形式 int main() {char n w;char* pa &n;*pa w;return 0; } 这并不是把字符串hello world放在n中&#xff0c;而是把第一个字符的地址…

【论文解读】System 2 Attention提高大语言模型客观性和事实性

一、简要介绍 本文简要介绍了论文“System 2 Attention (is something you might need too) ”的相关工作。基于transformer的大语言模型&#xff08;LLM&#xff09;中的软注意很容易将上下文中的不相关信息合并到其潜在的表征中&#xff0c;这将对下一token的生成产生不利影响…