【JavaEE】常见的锁策略都有哪些?

news2024/11/15 15:29:58

  • 博主简介:想进大厂的打工人
  • 博主主页:@xyk:
  • 所属专栏: JavaEE初阶

在Java多线程中,常见的锁策略都有哪些?这些锁策略应该怎么理解? (乐观锁vs悲观锁,轻量级锁vs重量级锁,自旋锁vs挂起等待锁,互斥锁vs读写锁,可重入锁vs不可重入锁,公平锁vs非公平锁)

常见的锁策略,注意: 接下来讲解的锁策略不仅仅是局限于 Java . 任何和 "锁" 相关的话题, 都可能会涉及到以下内容. 这些特性主要是给锁的实现者来参考的,普通的程序猿也需要了解一些, 对于合理的使用锁也是有很大帮助的.


目录

一、乐观锁vs悲观锁

1.1 乐观锁的功能

二、轻量级锁vs重量级锁

三、自旋锁vs挂起等待锁

四、互斥锁vs读写锁

五、可重入锁vs不可重入锁

六、公平锁vs非公平锁

七、相关面试题

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

7.2 介绍下读写锁?

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

7.4 synchronized 是可重入锁么?


一、乐观锁vs悲观锁

锁的实现者,预测接下来锁冲突的概率是大,还是不大,根据这个冲突的概率,来决定接下来该咋做~~

锁冲突就是锁竞争,俩个线程针对一个对象加锁,产生阻塞等待了~~

乐观锁vs悲观锁:

乐观锁:预测接下来冲突概率不大,做的工作会更少一些,效率更高一些(并不绝对)

悲观锁:预测接下来冲突概率比较大,做个工作要多一些,效率会低一些(并不绝对)

举个例子:

比如去年疫情放开了,有的人,乐观,觉得放开了也没啥事,也没有做啥准备,有的人,悲观,放开了影响会很大,于是就买了很多东西:药,吃的,酒精,手套等等....

1.1 乐观锁的功能

乐观锁的一个重要功能就是要检测出数据是否发生访问冲突. 我们可以引入一个 "版本号" 来解决

假设我们需要多线程修改 "用户账户余额"
设当前余额为 100. 引入一个版本号 version, 初始值为 1. 并且我们规定 "提交版本必须大于记录当前版本才能执行更新余额"
1) 线程 A 此时准备将其读出( version=1, balance=100 ),线程 B 也读入此信息( version=1,balance=100 )

2) 线程 A 操作的过程中并从其帐户余额中扣除 50( 100-50 ),线程 B 从其帐户余额中扣除 20( 100-20 );

3) 线程 A 完成修改工作,将数据版本号加1( version=2 ),连同帐户扣除后余额( balance=50),写回到内存中;

4) 线程 B 完成了操作,也将版本号加1( version=2 )试图向内存中提交数据( balance=80
),但此时比对版本发现,操作员 B 提交的数据版本号为 2 ,数据库记录的当前版本也为 2 ,不满足 “提交版本必须大于记录当前版本才能执行更新“ 的乐观锁策略。就认为这次操作失败.

二、轻量级锁vs重量级锁

轻量级锁:加锁解锁,过程更快更高效

重量级锁:加锁解锁,过程更慢,更低效

和乐观锁和悲观锁,虽然不是一回事,但是确实有一定的重合~~一个乐观锁很可能也是一个轻量级锁,一个悲观锁也很可能是一个重量级锁(不绝对)

synchronized 开始是一个轻量级锁. 如果锁冲突比较严重, 就会变成重量级锁.

三、自旋锁vs挂起等待锁

自旋锁:是轻量级锁的一种经典实现

伪码:

while (抢锁(lock) == 失败) {}

挂起等待锁:是重量级锁的一种经典实现

自旋锁会一直去尝试获取锁,一旦锁被释放,就第一时间拿到锁,速度会更快,无时无刻都要去尝试获取,干不了别的(忙等,消耗cpu资源)通常是纯用户态的,不需要经过内核态(时间相对更短)

挂起等待锁不会去一直尝试获取锁,如果锁被释放,不能第一时间拿到锁,可能需要过很久才能拿到锁,这个时间是空闲出来的,可以趁机做别的(不消耗cpu资源)(通过内核的机制,来实现挂起等待,时间更长)

想象一下, 去追求一个女神. 当男生向女神表白后, 女神说: 你是个好人, 但是我有男朋友了~~
挂起等待锁: 陷入沉沦不能自拔.... 过了很久很久之后, 突然女神发来消息, "咱俩要不试试?" (注意,这个很长的时间间隔里, 女神可能已经换了好几个男票了).
自旋锁: 死皮赖脸坚韧不拔. 仍然每天持续的和女神说早安晚安. 一旦女神和上一任分手, 那么就能立刻抓住机会上位.

针对上述三组策略,synchronized属于哪把锁?

synchronized即是乐观锁,也是悲观锁,即使轻量级锁,也是重量级锁,轻量级锁部分基于自旋锁实现,重量级锁部分基于挂起等待锁实现~~

四、互斥锁vs读写锁

多线程之间,数据的读取方之间不会产生线程安全问题,但数据的写入方互相之间以及和读者之间都需要进行互斥。如果两种场景下都用同一个锁,就会产生极大的性能损耗。所以读写锁因此而产生。

读写锁:能够把 读 和 写 两种加锁区分开,Java 标准库提供了 ReentrantReadWriteLock 类, 实现了读写锁.

ReentrantReadWriteLock.ReadLock 类表示一个读锁. 这个对象提供了 lock / unlock 方法进行加锁解锁.
ReentrantReadWriteLock.WriteLock 类表示一个写锁. 这个对象也提供了 lock / unlock 方法进行加锁解锁.

1.给读加锁

2.给写加锁

3.解锁

如果多个线程读同一个变量,不会涉及到线程安全问题!!!

读写锁中,约定:

1.读锁和读锁之间,不会锁竞争,不会产生阻塞等待(不会影响程序的速度,代码还是跑很快)

2.写锁和写锁之间,有锁竞争

3.读写和写锁之间,也有锁竞争

读写锁更适合于,一写多读的情况!!!

2,3点会减慢速度,但是保证准确性~~

互斥锁:

synchronized 不是读写锁,是互斥锁,加锁就只是单纯的加锁,没有更细化的区分了~~

像synchronized只有俩个操作:

1.进入代码块,加锁

2.出了代码块,解锁

五、可重入锁vs不可重入锁

可重入锁的字面意思是“可以重新进入的锁”,即允许同一个线程多次获取同一把锁。

如果一个锁,在一个线程中,连续对该锁加锁俩次,不死锁,就叫做可重入锁,如果死锁了,就叫不可能重入锁~~

按照之前对于锁的设定, 第二次加锁的时候, 就会阻塞等待. 直到第一次的锁被释放, 才能获取到第二个锁. 但是释放第一个锁也是由该线程来完成, 结果这个线程已经躺平了, 啥都不想干了, 也就无法进行解锁操作. 这时候就会 死锁

Java里只要以Reentrant开头命名的锁都是可重入锁,而且JDK提供的所有现成的Lock实现类,包括synchronized关键字锁都是可重入的

synchronized 是可重入锁

六、公平锁vs非公平锁

公平锁: 遵守 "先来后到".B 比 C 先来的. 当 A 释放锁的之后, B 就能先于 C 获取到锁.

非公平锁: 不遵守 "先来后到". B 和 C 都有可能获取到锁.


注意:

  • 操作系统内部的线程调度就可以视为是随机的. 如果不做任何额外的限制, 锁就是非公平锁. 如果要想实现公平锁, 就需要依赖额外的数据结构, 来记录线程们的先后顺序.
  • 公平锁和非公平锁没有好坏之分, 关键还是看适用场景.

synchronized是非公平锁

七、相关面试题

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

悲观锁认为多个线程访问同一个共享变量冲突的概率较大, 会在每次访问共享变量之前都去真正加锁.
乐观锁认为多个线程访问同一个共享变量冲突的概率不大. 并不会真的加锁, 而是直接尝试访问数据. 在访问的同时识别当前的数据是否出现访问冲突

7.2 介绍下读写锁?

读写锁就是把读操作和写操作分别进行加锁.
读锁和读锁之间不互斥.
写锁和写锁之间互斥.
写锁和读锁之间互斥.
读写锁最主要用在 "频繁读, 不频繁写" 的场景中

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

如果获取锁失败, 立即再尝试获取锁, 无限循环, 直到获取到锁为止. 第一次获取锁失败, 第二次的尝试会在极短的时间内到来. 一旦锁被其他线程释放, 就能第一时间获取到锁.

相比于挂起等待锁,

优点: 没有放弃 CPU 资源, 一旦锁被释放就能第一时间获取到锁, 更高效. 在锁持有时间比较短的场景下非常有用.
缺点: 如果锁的持有时间较长, 就会浪费 CPU 资源

7.4 synchronized 是可重入锁么?

是可重入锁.
可重入锁指的就是连续两次加锁不会导致死锁.
实现的方式是在锁中记录该锁持有的线程身份, 以及一个计数器(记录加锁次数). 如果发现当前加锁的线程就是持有锁的线程, 则直接计数自增

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

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

相关文章

Day919.生产就绪 -SpringBoot与K8s云原生微服务实践

生产就绪 Hi,我是阿昌,今天学习记录的是关于生产就绪的内容。 互联网软件交互阶段 如上,3个阶段中的“”生产就绪”是什么? 什么是生产就绪 生产就绪是指在生产过程中所需的一切准备工作已经完成,可以开始生产的状…

MySQL视图 视图的作用、视图常用语法

视图(View)是一种虚拟存在的表。视图中的数据并不在数据库中实际存在,行和列数据来自定义视图的查询中使用的表,并且是在使用视图时动态生成的。 通俗的讲,视图只保存了查询的SQL逻辑,不保存查询结果。 常…

Qt音视频开发27-ffmpeg视频旋转显示

一、前言 用手机或者平板拍摄的视频文件,很可能是旋转的,比如分辨率是1280x720,确是垂直的,相当于分辨率变成了720x1280,如果不做旋转处理的话,那脑袋必须歪着看才行,这样看起来太难受,所以一定要想办法解析到视频的旋转角度,然后根据这个角度重新绘制。在窗体那边也…

SpringBoot使用Spring Validation案例

简单使用 Java API规范(JSR303)定义了Bean校验的标准validation-api,但没有提供实现。hibernate validation是对这个规范的实现,并增加了校验注解如Email、Length等。 Spring Validation是对hibernate validation的二次封装,用于支持spring m…

Machine Learning-Ex4(吴恩达课后习题)Neural Networks Learning

目录 1. Neural Networks 1.1 Visualizing the data 1.2 Model representation 1.3 Feedforward and cost function 1.4 Regularized cost function 2. Backpropagation 2.1 Sigmoid gradient 2.2 Random initialization 2.3 Backpropagation 2.4 Gradient Checking…

工厂模式白话 - 3种都有哦

前言 工厂模式(Factory Pattern)里所谓的“工厂”和现实生活中的工厂一样 主要作用都是生产产品 像食品厂、服装厂、汽车厂生产吃的、穿的、开的 设计模式里的工厂则是生产对象 划分 工厂模式可分为简单工厂、工厂方法、抽象工厂3种 有啥不同呢&a…

RecvByteBufAllocator内存分配计算

虽然了解了整个内存池管理的细节,包括它的内存分配的具体逻辑,但是每次从NioSocketChannel中读取数据时,应该分配多少内存去读呢? 例如,客户端发送的数据为1KB , 应该分配多少内存去读呢? 例如:…

梳理ERP与CRM、MRP、PLM、APS、MES、WMS、SRM的关系

数字化转型中少不了ERP系统的存在,CRM、MRP、PLM、APS、MES、WMS、SRM这些系统都需要一起上吗? 如下图所示,是某企业IT系统集成架构流图。 先了解一下ERP是做什么的,ERP就是企业资源管理系统,从企业的价值链分析&…

在CSDN创作了6个月,我收获了什么?文末送书~

作者主页:阿玥的小东东主页! 正在学习:python和C/C 期待大家的关注哦 目录 一次很好的机会,让我开始了CSDN之旅 首先来看看我的几位领路人 创作动力 1W粉丝 在CSDN我收获了什么? 很高的展现量 认证创作者身份 社…

构建自动过程:FinalBuilder 8.0 Crack

使用 FinalBuilder 自动化您的构建过程很简单。使用 FinalBuilder,您无需编辑 xml 或编写脚本。可视化定义和调试您的构建脚本,然后使用 Windows 调度程序安排它们,或将它们与 Continua CI、Jenkins 或任何其他 CI 服务器集成。 成千上万的软…

手把手调参 YOLOv8 模型之 训练|验证|推理配置-详解

YOLO系列模型在目标检测领域有着十分重要的地位,随着版本不停的迭代,模型的性能在不断地提升,源码提供的功能也越来越多,那么如何使用源码就显得十分的重要,接下来通过文章带大家手把手去了解Yolov8(最新版…

Android开发—Jetpack四件套

2017年,Google发布了Android Architecture Components,包括Room、LiveData、ViewModel和Paging等组件,旨在帮助开发者更轻松地实现MVVM架构。 2018年,Google在I/O大会上推出的一套Android开发组件库,旨在帮助开发者更…

Python 小型项目大全 56~60

五十六、质数 原文:http://inventwithpython.com/bigbookpython/project56.html 质数是只能被 1 和它自己整除的数。质数有各种各样的实际应用,但是没有算法可以预测它们;我们必须一次计算一个。然而,我们知道有无限多的质数有待发…

技术招聘漫谈 | Java工程师招聘难?你可能需要这份独家指南

两周前,我们发布了一篇关于怎样招聘前端工程师的文章(点击此处顾)。在文章中,我们分析了前端岗位有哪些必不可少的考察要点,以及如何在面试中考核对方是否能写出高质量的代码,这篇文章得到了大量技术面试官…

高完整性系统工程(四): An Overview of Alloy

目录 1. 概述 2. 指定软件设计 3. 验证设计规范 4. 验证预期属性 1. 概述 在第一章中,我们将解释如何使用 Alloy 来探索一个非常简单的软件组件的设计,即大多数操作系统中存在的众所周知的垃圾箱 或回收站。目的是对如何使用 Alloy 指定和分析软件设…

MyBatis注解开发---实现自定义映射关系和关联查询

目录 一、使用注解实现自定义映射关系 1. 编写注解方法 2. 编写测试方法 3. 查看运行结果 二、使用注解实现一对一关联查询 1. 编写注解方法 2. 编写测试方法 3. 查看运行结果 三、使用注解实现一对多关联查询 1. 编写注解方法 2. 编写测试方法 3. 查看运行结果 四…

List接口中的ArrayList与LinkedList

ArrayList ArrayList的继承实现关系图 ArrayList 底层就是⼀个 Object[] 数组,当实例化ArrayList时没有指定数组容量大小,、第⼀次添加元素(调⽤ add() ⽅法)时会初始化为⼀个⻓度为 10 的数组(即默认初始化容量为 1…

[Netty源码] ByteBufAllocator内存管理器相关问题 (十一)

文章目录1.ByteBufAllocator 内存管理器2.UnpooledByteBufAllocator2.1 heap内存的分配2.2 direct内存的分配3.PooledByteBufAllocator3.1 heap内存和direct内存的分配3.2 directArena分配direct内存的流程3.3 内存规格的介绍4.缓存的相关问题4.1 缓存的数据结果4.2 命中缓存的…

一维差分思想【算法推导、深刻思考】

797. 差分 - AcWing题库 差分本质上就是前缀和的逆运算 算法推导 其实在最开始自己去完成这个题目的时候,感觉好像是可以往前缀和方向靠的,但是一下子没有想到实现方法就无疾而终了。所以最后选择的算法就只是单纯的暴力(虽然知道过不了&…

【操作系统复习】第5章 存储器管理

存储器的层次结构 存储层次 ➢ CPU寄存器 ➢ 主存:高速缓存、主存储器、磁盘缓存 ➢ 辅存:固定磁盘、可移动介质 层次越高,访问速度越快,价格也越高,存储容量也最小 寄存器和主存掉电后存储的信息不再存在&a…