【JavaEE初阶系列】——常见的锁策略

目录

🚩乐观锁和悲观锁

🚩读写锁和普通互斥锁

🚩轻量级锁和重量级锁

🚩自旋锁和挂起等待锁

🚩公平锁和非公平锁

🚩可重入锁和不可重入锁

🚩关于synchronized的锁策略以及自适应


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

🚩乐观锁和悲观锁

 这两种锁是站在加锁解锁的角度看待的,看的是加锁解锁的过程中所干的活是多还是少

悲观和乐观是对后续锁冲突是否激烈(频繁)给出的预测。

  • 如果预测接下来锁冲突的概率不大,就可以少做一些工作,就称为 乐观锁
  • 如果预测接下来锁冲突的概率很大,就应该多做一些工作,就称为 悲观锁
悲观锁: 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。
举个例子:
同学 A 认为 " 老师是比较忙的 , 我来问问题 , 老师不一定有空解答 ". 因此同学 A 会先给老师发消息 : " 老师你忙嘛? 我下午两点能来找你问个问题嘛 ?" ( 相当于加锁操作 ) 得到肯定的答复之后 , 才会真的来问问题 .如果得到了否定的答复, 那就等一段时间 , 下次再来和老师确定时间 . 这个是悲观锁。
乐观锁: 假设数据一般情况下不会产生并发冲突,所以在数据进行提交更新的时候,才会正式对数据是否产生并 发冲突进行检测,如果发现并发冲突了,则让返回用户错误的信息,让用户决定如何去做。
举个例子:
同学 B 认为 " 老师是比较闲的 , 我来问问题 , 老师大概率是有空解答的 ". 因此同学 B 直接就来找老师 .( 没加锁, 直接访问资源 ) 如果老师确实比较闲 , 那么直接问题就解决了 . 如果老师这会确实很忙 , 那么同学 B也不会打扰老师, 就下次再来 ( 虽然没加锁 , 但是能识别出数据访问冲突 ). 这个是乐观锁。
这两种思路不能说谁优谁劣, 而是看当前的场景是否合适.
  • 如果当前老师确实比较忙, 那么使用悲观锁的策略更合适, 使用乐观锁会导致 "白跑很多趟", 耗费额外的资源。
  • 如果当前老师确实比较闲, 那么使用乐观锁的策略更合适, 使用悲观锁会让效率比较低.

Synchronized 初始使用乐观锁策略. 当发现锁竞争比较频繁的时候, 就会自动切换成悲观锁策略.

就好比同学 C 开始认为 "老师比较闲的", 问问题都会直接去找老师.
但是直接来找两次老师之后, 发现老师都挺忙的, 于是下次再来问问题, 就先发个消息问问老师忙不忙, 再决定是否来问问题。

🚩读写锁和普通互斥锁

读写锁:

  • Ⅰ:加读锁
  •  Ⅱ:加写锁

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

读写锁( readers-writer lock ),看英文可以顾名思义,在执行加锁操作时需要额外表明读写意图,复数读者之间并不互斥,而写者则要求与任何人互斥。
  • 读锁和读锁之间不会产生竞争
  • 写锁和写锁之间会产生竞争
  • 读锁和写锁会产生竞争
一个线程对于数据的访问, 主要存在两种操作: 读数据 和 写数据.
  • 两个线程都只是读一个数据, 此时并没有线程安全问题. 直接并发的读取即可.
  • 两个线程都要写一个数据, 有线程安全问题.
  • 一个线程读另外一个线程写, 也有线程安全问题.

读写锁就是把读操作和写操作区分对待. Java 标准库提供了 ReentrantReadWriteLock 类, 实现了读写锁.
  • ReentrantReadWriteLock.ReadLock 类表示一个读锁. 这个对象提供了 lock / unlock 方法进行加锁解锁.
  • ReentrantReadWriteLock.WriteLock 类表示一个写锁. 这个对象也提供了 lock / unlock 方法进行加锁解锁.
读写锁特别适合于 "频繁读, 不频繁写" 的场景中. (这样的场景其实也是非常广泛存在的).
比如对一个系统管理
  • 每节课老师都会进行点名,点名就需要查看班级的同学列表(读操作),这个操作可能要每周执行好几次。
  • 而什么时候修改同学列表呢?(写操作),就新同学加入的时候,可能一个月都不必改一次。
  • 再比如,同学们使用教务系统查看作业(读操作),一个班级的同学很多,读操作一天就要进行几十次上百次。
  • 但是这一节课的作业,老师只布置了一次(写操作)

普通互斥锁:就如同Synchronized,当两个线程竞争同一把锁的时候就会产生阻塞等待。synchronize不是读写锁,只是普通互斥锁。

多个线程同时读一个变量并没有问题,而且读的场景相比于写的场景就多了很多,使用读写锁相比于普通互斥锁就减少了很多的锁竞争,大大的优化了效率。(因为我们上述说明了,读写锁在读锁和读锁之间不会产生竞争)


🚩轻量级锁和重量级锁

  • 轻量级锁的加锁解锁开销比较少,典型的是纯用户态的加锁解锁逻辑,开销是比较少的
  • 重量级锁的加锁解锁开销比较大,典型的是进入了系统内核态的加锁解锁逻辑,开销是比较大的

这两种锁是站在结果的角度看待最终加锁解锁消耗的时间是多还是少,和乐观锁与悲观锁并不一样通常情况下乐观锁比较轻量,悲观锁比较重量,但是也并不绝对。


🚩自旋锁和挂起等待锁

  • 自旋锁:相当于是"忙等"的状态,大量消耗的CPU资源,反复询问当前锁是否就绪。
  • 挂起等待锁:把CPU资源空闲出来去做其他的事情,过一段时间才询问当前锁是否就绪。

自旋锁:

按之前的方式,线程在抢锁失败后进入阻塞状态,放弃 CPU ,需要过很久才能再次被调度 .
但实际上 , 大部分情况下,虽然当前抢锁失败,但过不了很久,锁就会被释放。没必要就放弃 CPU. 这个时候就可以使用自旋锁来处理这样的问题。
自旋锁伪代码:
while (抢锁(lock) == 失败) {}
如果获取锁失败 , 立即再尝试获取锁 , 无限循环 , 直到获取到锁为止 . 第一次获取锁失败 , 第二次的尝试会在极短的时间内到来. 一旦锁被其他线程释放, 就能第一时间获取到锁 .

理解自旋锁 vs 挂起等待锁

举个例子:

在我们等人的时候,对方还没有到达约定的地点,一直反复的打电话催促就是自旋锁,而当你发现对方还没到的时候,就在约定的地方找个地方玩手机,叫他来了再在约定的地点旁边找我们一下,多消耗一点时间,却能够用这些时间去做其他的事情,时间被利用起来了,这就是挂起等待锁。

自旋锁是轻量级锁的一种体现,挂起等待锁是重量级锁的一种体现。



🚩公平锁和非公平锁

  • 公平锁:公平锁是先来后到,谁先来谁就拿到锁
  • 非公平锁:多个线程同时竞争一把锁,有一个线程是比较晚来的,却比其他先来的线程先拿到锁

举个例子:

  • t1,t2,t3三个线程竞争同一把锁,t1先来的,所以t1先拿到了锁,这就叫公平锁.
  • 而如果t3是晚来的,然后t3比其他两个线程先拿到了锁,这就叫非公平锁

操作系统默认的锁的调度,是非公平的情况,想要实现一个公平锁,就需要引入额外的数据结构,来记录线程加锁的顺序,需要一定的额外开销。

公平锁和非公平锁没有好坏之分 , 关键还是看适用场景 .
synchronized 是非公平锁 .

🚩可重入锁和不可重入锁

  • 可重入锁的字面意思是可以重新进入的锁,即允许同一个线程多次获取同一把锁
  • 比如一个递归函数里有加锁操作,递归过程中这个锁会阻塞自己吗?如果不会,那么这个锁就是可重入(因为这个原因可重入锁也叫做递归锁

可重入锁:同一个线程对同一把锁连续加锁两次不会造成死锁

不可重入锁:同一个线程对同一把锁连续加锁两次会造成死锁

理解 "把自己锁死"
一个线程没有释放锁, 然后又尝试再次加锁.
// 第一次加锁, 加锁成功
lock();
// 第二次加锁, 锁已经被占用, 阻塞等待. 
lock();
按照之前对于锁的设定, 第二次加锁的时候, 就会阻塞等待. 直到第一次的锁被释放, 才能获取到第二个锁. 但是释放第一个锁也是由该线程来完成, 结果这个线程已经躺平了, 啥都不想干了, 也就无法进行解锁操作. 这时候就会 死锁。
这样的锁称为 不可重入锁.
synchronized 是可重入锁 (因为我们之前在synchronized中说到了死锁,一个线程,连续针对一把锁,加锁两次,不会出现 死锁 )但是俩个线程俩把锁, 线程t1获取锁A,t2线程获取锁,B 线程t1获取锁B,t2线程获取锁A.这样大概率是会导致死锁现象。

🚩关于synchronized的锁策略以及自适应

①既是乐观锁,也是悲观锁

②既是轻量级锁,也是重量级锁

③既是自旋锁,也是挂起等待锁

④不是读写锁,是普通互斥锁

⑤是非公平锁

⑥是可重入锁

  • 初始情况下,synchronized会预测当前的锁冲突的概率不大。此时以乐观锁的模式来运行。(此时也就是轻量级锁,基于自旋锁方式实现)
  • 在实际使用过程中,如果发现锁冲突的情况比较多,synchronized就会升级成 悲观锁(也就是重量级锁,基于挂起等待的方式实现)

synchronized是自适应的,初始使用的时候,是 乐观锁/轻量级锁/自旋锁,如果竞争不激烈则保持这个状态不变,如果锁竞争激烈了,synchronized会自动升级成为 悲观锁/重量级锁/挂起等待锁,所以synchronized是"智能"的。


能“开心的话,当笨蛋也没关系".

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

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

相关文章

transformers微调模型后使用pieline调用无法预测列表文本

初学transformers框架 使用trainer简单训练一个文本分类模型三个epoch后 使用piepline调用model 和tokenizer后 发现 传入列表文本后 输出就变得不正常了,为么子哇 如下图

语义分割——Dark Zurich数据集

一、重要性及意义 首先,Dark Zurich为语义分割提供了大量真实且多样化的图像数据。该数据集包含了在夜间、黄昏和白天拍摄的大量图像,涵盖了不同光照条件和场景下的图像变化。这些图像数据不仅丰富了语义分割任务的数据集,也为模型提供了更全…

LLM:函数调用(Function Calling)

1 函数调用 虽然大模型能解决很多问题,但大模型并不能知晓一切。比如,大模型不知道最新消息(GPT-3.5 的知识截至 2021年9月,GPT-4 是 2023 年12月)。另外,大模型没有“真逻辑”。它表现出的逻辑、推理,是训练文本的统计…

IDEA一键备份MySQL数据库(mysqldump版)

问题 又到了搬MySQL数据库的时刻,这次我不想使用命令行备份,这次我想使用IDEA一键备份MySQL数据库。 解决 假设安装好mysqldump命令后,让IDEA使用mysqldump一键备份指定的数据库。具体IDEA配置如下: 这是IDEA上面的数据库到处…

掌握未来商机:如何利用会话式AI赢在起跑线

AI智能助手:提升工作效率的秘密武器 在这个信息爆炸的时代,内容策略成为了品牌与用户之间沟通的重要桥梁。一个有效的内容策略能够帮助品牌提升知名度,建立与目标受众的深度连接,并最终实现转化目标。内容策略不仅涉及内容的创作与…

【Pytorch学习笔记(二)】张量的创建(补充)

一、知识回顾 我们在博客《张量的创建与访问》中已经讨论了一些张量的创建方法如torch.CharTensor()、torch.FloatTensor()以及torch.zeros()等张量创建方法,但由于其仅仅介绍了cpu版本torch下张量的创建方法和只有具体数据类型张量,本节内容旨在补充gp…

深入理解MapReduce:从Map到Reduce的工作原理解析

当谈到分布式计算和大数据处理时,MapReduce是一个经典的范例。它是一种编程模型和处理框架,用于在大规模数据集上并行运行计算任务。MapReduce包含三个主要阶段:Map、Shuffle 和 Reduce。 ** Map 阶段 ** Map 阶段是 MapReduce 的第一步&am…

微信开发者工具创建一个小程序

创建项目 对于上面这个AppID可以自行选择是注册还是测试号,我是使用的测试号,之后再下面选择模板,我这里选择了JS-基础模板。 进入项目后在模拟器中可看到如下页面: 添加提交按钮进行页面跳转 添加需要跳转的文件夹,…

Node.js------模块化

◆ 能够说出模块化的好处◆ 能够知道 CommonJS 规定了哪些内容◆ 能够说出 Node.js 中模块的三大分类各自是什么◆ 能够使用 npm 管理包◆ 能够了解什么是规范的包结构◆ 能够了解模块的加载机制 一.模块化的基本概念 1.模块化 模块化是指解决一个复杂问题时&#xff0c…

Express

可以方便、快速创建Web网站的服务器(提供web网页资源)或API接口服务器(提供API接口) app.get(请求URL,function(req,res)>{}) //req:请求对象(包括请求属性和方法) //res:响应对象&#xff…

Go 源码之 gin 框架

Go 源码之 gin 框架 go源码之gin - Jxy 博客 一、总结 gin.New()初始化一个实例:gin.engine,该实例实现了http.Handler接口。实现了ServeHTTP方法 注册路由、注册中间件,调用addRoute将路由和中间件注册到 methodTree 前缀树(节…

HashSet解析

文章目录 集合简介对HashSet进行遍历迭代器增强forLambda表达式 Hash底层原理 集合简介 HashSet是Set集合下的子接口,set集合添加的元素是无索引,不重复,无序,与List系列集合正好相反。 无序:存储顺序不一致。 不重复…

全新的分布式锁,几行代码搞定,简单且强大

# 前言 分布式锁是分布式系统中一个极为重要的工具。目前有多种分布式锁的设计方案,比如借助 redis,mq,数据库,zookeeper 等第三方服务系统来设计分布式锁。tldb 提供的分布式锁,主要是要简化这个设计的过程&#xff0…

数据挖掘入门项目二手交易车价格预测之特征工程

文章目录 目标常见的特征工程具体步骤1. 导入数据2. 删除异常值3. 特征构造3.1 为树模型构造特征3.2 为LR NN 之类的模型构造特征 4. 特征筛选过滤式包裹式嵌入式 5. 总结 本文数据集来自阿里天池:https://tianchi.aliyun.com/competition/entrance/231784/informat…

初始Java篇(JavaSE基础语法)(5)(类和对象(下))

个人主页(找往期文章包括但不限于本期文章中不懂的知识点):我要学编程(ಥ_ಥ)-CSDN博客 目录 封装 访问限定符 封装扩展之包 自定义包 static成员 static修饰成员变量 static修饰成员方法 static成员变量初始化 内部类 对象的打…

Adaboost集成学习 | Matlab实现基于SVM-Adaboost支持向量机结合Adaboost集成学习时间序列预测(股票价格预测)

目录 效果一览基本介绍模型设计程序设计参考资料效果一览 基本介绍 Adaboost集成学习 | 基于SVM-Adaboost支持向量机结合Adaboost集成学习时间序列预测(股票价格预测)基于SVM(支持向量机)和AdaBoost集成学习的时间序列预测(如股票价格预测)是一种结合了两种强大机器学习算…

《极客时间TonyBai go语言第一课》学习笔记

文章目录 前置篇显式组合并发 入门篇大纲 前置篇 显式 在 C 语言中&#xff0c;下面这段代码可以正常编译并输出正确结果&#xff1a; #include <stdio.h> int main() { short int a 5; int b 8; long c 0; c a b; printf("%ld\n", c); }我们看到在上面…

Windows 电脑麦克风 自动启用/禁用 小玩具!

WinMicrophone Windows 系统的 麦克风设备&#xff08;启用/禁用&#xff09;切换驱动&#xff01;它是小巧且快速的&#xff0c;它能够自动的检测并切换麦克风的情况。 您可以在软件包仓库中找到发布版本的exe包&#xff0c;无需安装&#xff01;其能够大大增大您在Windows中…

【网站项目】面向社区健康服务的医疗平台

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

【MATLAB源码-第25期】基于matlab的8QAM调制解调仿真,手动实现未调用内置函数,星座图展示。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 8QAM调制&#xff08;8 Quadrature Amplitude Modulation&#xff09;是一种数字调制技术&#xff0c;它可以在有限带宽内传输更多的信息比特。在8QAM调制中&#xff0c;每个符号可以携带3个比特的信息。QAM调制是将数字信号…