Java中常见的锁策略

news2024/10/6 4:12:30

目录

 乐观锁 vs 悲观锁

悲观锁:

乐观锁:

重量级锁 vs 轻量级锁  

⾃旋锁(Spin Lock)

公平锁 vs 非公平锁  

可重⼊锁 vs 不可重入锁  

读写锁 

 


乐观锁 vs 悲观锁

悲观锁:
总是假设最坏的情况,每次去拿数据的时候都认为别⼈会修改,所以每次在拿数据的时候都会上锁,这样别⼈想拿这个数据就会阻塞直到它拿到锁。
乐观锁:
假设数据⼀般情况下不会产⽣并发冲突,所以在数据进⾏提交更新的时候,才会正式对数据是否产⽣并发冲突进⾏检测,如果发现并发冲突了,则让返回⽤⼾错误的信息,让⽤⼾决定如何去做。

举个栗⼦: 同学 A 和 同学 B 想请教⽼师⼀个问题.
同学 A 认为 "⽼师是⽐较忙的, 我来问问题, ⽼师不⼀定有空解答". 因此同学 A 会先给⽼师发消息: "⽼师你忙嘛? 我下午两点能来找你问个问题嘛?" (相当于加锁操作) 得到肯定的答复之后, 才会真的来问问题. 如果得到了否定的答复, 那就等⼀段时间, 下次再来和⽼师确定时间. 这个是悲观锁.

 同学 B 认为 "⽼师是⽐较闲的, 我来问问题, ⽼师⼤概率是有空解答的". 因此同学 B 直接就来找⽼师.(没加锁, 直接访问资源) 如果⽼师确实⽐较闲, 那么直接问题就解决了. 如果⽼师这会确实很忙, 那么同学 B也不会打扰⽼师, 就下次再来(虽然没加锁, 但是能识别出数据访问冲突). 这个是乐观锁.

这两种思路不能说谁优谁劣, ⽽是看当前的场景是否合适.
如果当前⽼师确实⽐较忙, 那么使⽤悲观锁的策略更合适, 使⽤乐观锁会导致 "⽩跑很多趟", 耗费额外的资源.
如果当前⽼师确实⽐较闲, 那么使⽤乐观锁的策略更合适, 使⽤悲观锁会让效率⽐较低.

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

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

重量级锁 vs 轻量级锁  

锁的核⼼特性 "原⼦性", 这样的机制追根溯源是 CPU 这样的硬件设备提供的.
  • CPU 提供了 "原⼦操作指令".
  • 操作系统基于 CPU 的原⼦指令, 实现了 mutex 互斥锁.
  • JVM 基于操作系统提供的互斥锁, 实现了 synchronized ReentrantLock 等关键字和类.

 注意, synchronized 并不仅仅是对 mutex 进⾏封装, 在 synchronized 内部还做了很多其

他的⼯作
重量级锁: 加锁机制重度依赖了 OS 提供了 mutex
  • ⼤量的内核态⽤⼾态切换
  • 很容易引发线程的调度

这两个操作, 成本⽐较⾼. ⼀旦涉及到⽤⼾态和内核态的切换, 就意味着 "沧海桑⽥" 

 轻量级锁: 加锁机制尽可能不使⽤ mutex, ⽽是尽量在⽤⼾态代码完成. 实在搞不定了, 再使⽤ mutex.

  • 少量的内核态⽤⼾态切换.
  • 不太容易引发线程调度.
理解⽤⼾态 vs 内核态
想象去银⾏办业务.
在窗⼝外, ⾃⼰做, 这是⽤⼾态. ⽤⼾态的时间成本是⽐较可控的.
在窗⼝内, ⼯作⼈员做, 这是内核态. 内核态的时间成本是不太可控的.
如果办业务的时候反复和⼯作⼈员沟通, 还需要重新排队, 这时效率是很低的.

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

自旋锁(Spin Lock)

按之前的⽅式,线程在抢锁失败后进⼊阻塞状态,放弃 CPU,需要过很久才能再次被调度.
但实际上, ⼤部分情况下,虽然当前抢锁失败,但过不了很久,锁就会被释放。没必要就放弃 CPU. 这个时候就可以使⽤⾃旋锁来处理这样的问题.
如果获取锁失败, ⽴即再尝试获取锁, ⽆限循环, 直到获取到锁为⽌. 第⼀次获取锁失败, 第⼆次的尝试会在极短的时间内到来. ⼀旦锁被其他线程释放, 就能第⼀时间获取到锁
理解⾃旋锁 vs 挂起等待锁
想象⼀下, 去追求⼀个⼥神. 当男⽣向⼥神表⽩后, ⼥神说: 你是个好⼈, 但是我有男朋友了~~
挂起等待锁: 陷⼊沉沦不能⾃拔.... 过了很久很久之后, 突然⼥神发来消息, "咱俩要不试试?" (注意, 这个很⻓的时间间隔⾥, ⼥神可能已经换了好⼏个男票了).
⾃旋锁: 死⽪赖脸坚韧不拔. 仍然每天持续的和⼥神说早安晚安. ⼀旦⼥神和上⼀任分⼿, 那么就能⽴刻抓住机会上位.
⾃旋锁是⼀种典型的 轻量级锁 的实现⽅式.
  • 优点: 没有放弃 CPU, 不涉及线程阻塞和调度, ⼀旦锁被释放, 就能第⼀时间获取到锁.
  • 缺点: 如果锁被其他线程持有的时间⽐较久, 那么就会持续的消耗 CPU 资源. (⽽挂起等待的时候是不消耗 CPU 的)

synchronized 中的轻量级锁策略⼤概率就是通过⾃旋锁的⽅式实现的.  

公平锁 vs 非公平锁  

假设三个线程 A, B, C. A 先尝试获取锁, 获取成功. 然后 B 再尝试获取锁, 获取失败, 阻塞等待; 然后 C 也尝试获取锁, C 也获取失败, 也阻塞等待.
当线程 A 释放锁的时候, 会发⽣啥呢?
公平锁: 遵守 "先来后到". B ⽐ C 先来的. 当 A 释放锁的之后, B 就能先于 C 获取到锁.
⾮公平锁: 不遵守 "先来后到". B 和 C 都有可能获取到锁
这就好⽐⼀群男⽣追同⼀个⼥神. 当⼥神和前任分⼿之后, 先来追⼥神的男⽣上位, 这就是公平锁; 如果
是⼥神不按先后顺序挑⼀个⾃⼰看的顺眼的, 就是⾮公平锁.
注意:
  • 操作系统内部的线程调度就可以视为是随机的. 如果不做任何额外的限制, 锁就是⾮公平锁. 如果要想实现公平锁, 就需要依赖额外的数据结构, 来记录线程们的先后顺序.
  • 公平锁和⾮公平锁没有好坏之分, 关键还是看适⽤场景.

 synchronized 是⾮公平锁.

可重入锁 vs 不可重入锁  

可重⼊锁的字⾯意思是“可以重新进⼊的锁”,即允许同⼀个线程多次获取同⼀把锁。
⽐如⼀个递归函数⾥有加锁操作,递归过程中这个锁会阻塞⾃⼰吗?如果不会,那么这个锁就是可重⼊锁(因为这个原因可重⼊锁也叫做递归锁)。
Java⾥只要以Reentrant开头命名的锁都是可重⼊锁,⽽且JDK提供的所有现成的Lock实现类,包括 synchronized关键字锁都是可重⼊的
⽽ Linux 系统提供的 mutex 是不可重⼊锁.
理解 "把⾃⼰锁死"
⼀个线程没有释放锁, 然后⼜尝试再次加锁.
// 第⼀次加锁, 加锁成功
 lock();
 // 第⼆次加锁, 锁已经被占⽤, 阻塞等待. 
 lock();
按照之前对于锁的设定, 第⼆次加锁的时候, 就会阻塞等待. 直到第⼀次的锁被释放, 才能获取到第⼆个锁. 但是释放第⼀个锁也是由该线程来完成, 结果这个线程已经躺平了, 啥都不想⼲了, 也就⽆法进⾏解锁操作. 这时候就会 死锁.

 

 这样的锁称为 不可重⼊锁.

 synchronized 是可重入锁

读写锁 

多线程之间,数据的读取⽅之间不会产⽣线程安全问题,但数据的写⼊⽅互相之间以及和读者之间都 需要进⾏互斥。如果两种场景下都⽤同⼀个锁,就会产⽣极⼤的性能损耗。所以读写锁因此而产⽣。
读写锁(readers-writer lock),看英⽂可以顾名思义,在执⾏加锁操作时需要额外表明读写意图,复数读者之间并不互斥,⽽写者则要求与任何⼈互斥。
⼀个线程对于数据的访问, 主要存在两种操作: 读数据 和 写数据.
  • 两个线程都只是读⼀个数据, 此时并没有线程安全问题. 直接并发的读取即可.
  • 两个线程都要写⼀个数据, 有线程安全问题.
  • ⼀个线程读另外⼀个线程写, 也有线程安全问题.
读写锁就是把读操作和写操作区分对待. Java 标准库提供了 ReentrantReadWriteLock 类, 实现
了读写锁
  • ReentrantReadWriteLock.ReadLock 类表⽰⼀个读锁. 这个对象提供了 lock / unlock ⽅法 进⾏加锁解锁.
  • ReentrantReadWriteLock.WriteLock 类表⽰⼀个写锁. 这个对象也提供了 lock / unlock ⽅法进⾏加锁解锁
其中,
  • 读加锁和读加锁之间, 不互斥.
  • 写加锁和写加锁之间, 互斥.
  • 读加锁和写加锁之间, 互斥.
注意, 只要是涉及到 "互斥", 就会产⽣线程的挂起等待. ⼀旦线程挂起, 再次被唤醒就不知道隔了多久了.
因此尽可能减少 "互斥" 的机会, 就是提⾼效率的重要途径

 读写锁特别适合于 "频繁读, 不频繁写" 的场景中. (这样的场景其实也是⾮常⼴泛存在的).

 Synchronized 不是读写锁.

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

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

相关文章

蓝桥杯第1593题——二进制问题

题目描述 小蓝最近在学习二进制。他想知道 1 到 N 中有多少个数满足其二进制表示中恰好有 K 个 1。你能帮助他吗? 输入描述 输入一行包含两个整数 N 和 K。 输出描述 输出一个整数表示答案。 输入输出样例 示例 输入 7 2输出 3评测用例规模与约定 对于 30% …

YOLOv9改进策略 :主干优化 | 极简的神经网络VanillaBlock 实现涨点 |华为诺亚 VanillaNet

💡💡💡本文改进内容: VanillaNet,是一种设计优雅的神经网络架构, 通过避免高深度、shortcuts和自注意力等复杂操作,VanillaNet 简洁明了但功能强大。 💡💡💡引入VanillaBlock GFLOPs从原始的238.9降低至 165.0 ,保持轻量级的同时在多个数据集验证能够高效涨点…

跑spark的yarn模式时RM连不上的情况

在linux控制台跑spark on yarn一个测试案例,日志中总显示RM连yarn服务的时候是:0.0.0.0:8032 具体情况如下图: 我问题出现的原因,总结如下: 1.防火墙没关闭,关闭 2.spark-env.sh这个文件的YARN_CONF_DIR…

NRF52832修改OTA升级时的bootloader蓝牙MAC

NRF52832在OTA升级时,修改了APP的蓝牙MAC会导致无法升级,原因是OTA程序的蓝牙MAC没有被修改所以手机扫描蓝牙时无法连接 解决办法 在bootloader的程序里面加入修改蓝牙mac地址的代码实现原理: 在bootloader蓝牙广播开启之前修改蓝牙mac 通…

Java 处理Mysql获取树形的数据

Mysql数据&#xff1a; 代码如下&#xff1a; Entity&#xff1a; Data Accessors(chain true) public class Region {private BigInteger id;//名称private String name;//父idprivate BigInteger parentId;private List<Region> children;private Integer createTim…

基于SSM+Jsp+Mysql的固定资产管理系统

开发语言&#xff1a;Java框架&#xff1a;ssm技术&#xff1a;JSPJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包…

linux系统中启动MyCat问题总结

最近在深入学习mysql的底层原理和应用&#xff0c;今天在学习分库分表操作的时候&#xff0c;由于一些配置问题&#xff0c;导致无法正常启动MyCat。最终通过GPT和其他博客将问题解决了&#xff0c;以下是一篇关于MyCat启动异常的解决方案。 1.思路&#xff1a; 配置问题其实…

python练习二

# Demo85def pai_xu(ls_test):#创建一个列表排序函数命名为pai_xu# 对创建的函数进行注释"""这是一个关于列表正序/倒序排列的函数:param ls_test: 需要排序的列表:return:"""ls1 [int(ls_test[i]) for i in range(len(ls_test))]#对input输入的…

求组合数I(acwing)

题目描述&#xff1a; 给定n组询问&#xff0c;每组询问给定两个整数a&#xff0c;b&#xff0c;请你输出Ca^b mod(1e97)的值。 输入格式: 第一行包含整数n。 接下来n行&#xff0c;每行包含一组a和b。 输出格式: 共n行&#xff0c;每行输出一个询问的解。 …

C++刷题篇——05静态扫描

一、题目 二、解题思路 注意&#xff1a;注意理解题目&#xff0c;缓存的前提是先扫描一次 1、使用两个map&#xff0c;两个map的key相同&#xff0c;map1&#xff1a;key为文件标识&#xff0c;value为文件出现的次数&#xff1b;map2&#xff1a;key为文件标识&#xff0c;va…

Navicat设置mysql权限

新建用户&#xff1a; 注意&#xff1a;如果不生效执行刷新命令:FLUSH PRIVILEGES; 执行后再重新打开查看&#xff1b; 查询权限命令&#xff1a;1234为新建的用户名&#xff0c;localhost为访问的地址 SHOW GRANTS FOR 1234localhost;如果服务器设置服务器权限后可能会出现权…

【Docker】搭建安全可控的自定义通知推送服务 - Bark

【Docker】搭建安全可控的自定义通知推送服务 - Bark 前言 本教程基于绿联的NAS设备DX4600 Pro的docker功能进行搭建。 简介 Bark是一款为Apple设备用户设计的开源推送服务应用&#xff0c;它允许开发者、程序员以及一般用户将信息快速推送到他们自己的iPhone、iPad等设备上…

副业赚钱攻略:给工资低的你6个实用建议,闷声致富不是梦

经常有朋友向我咨询&#xff0c;哪些副业比较靠谱且能赚钱。实际上&#xff0c;对于大多数打工族而言&#xff0c;副业不仅是增加收入的途径&#xff0c;更是利用业余时间提升自我、实现价值的重要方式。 鉴于此&#xff0c;今天我想和大家分享六个值得尝试的副业&#xff0c;…

linux:生产者消费者模型

个人主页 &#xff1a; 个人主页 个人专栏 &#xff1a; 《数据结构》 《C语言》《C》《Linux》 文章目录 前言一、生产者消费者模型二、基于阻塞队列的生产者消费者模型代码实现 总结 前言 本文是对于生产者消费者模型的知识总结 一、生产者消费者模型 生产者消费者模型就是…

Java封装最佳实践:打造高内聚、低耦合的优雅代码~

​ 个人主页&#xff1a;秋风起&#xff0c;再归来~ 文章专栏&#xff1a;javaSE的修炼之路 个人格言&#xff1a;悟已往之不谏&#xff0c;知来者犹可追 克心守己&#xff0c;律己则安&#xff01; 1、封装 1.1 封装的概念 面向对象程序三大…

数据类型和变量的深入理解

引言&#xff1a;C语言数据类型的意义&#xff0c;数据在内存中的存储情况&#xff0c;变量的声明与定义的区别&#xff0c;和一些关键字。 目录 1.变量的定义与声明 1.1定义与声明 1.2 变量的初始化与赋值 2.C语言常见的数据类型 3.变量的作用域与生命周期 4.signed 和 un…

SpringBoot mybatis-starter解析

mybatis-starter使用指南 自动检测工程中的DataSource创建并注册SqlSessionFactory实例创建并注册SqlSessionTemplate实例自动扫描mappers mybatis-starter原理解析 注解类引入原理 查看对应的autoconfigure包 MybatisLanguageDriverAutoConfiguration 主要是协助使用注解来…

Leetcode 4.1

LeetCode 热题 100 贪心算法1.买卖股票的最佳时机2.跳跃游戏3.跳跃游戏 II4.划分字母区间 区间合并1.合并区间 贪心算法 1.买卖股票的最佳时机 买卖股票的最佳时机 买的那天一定是卖的那天之前的最小值。 每到一天&#xff0c;维护那天之前的最小值即可。 在题目中&#xff0…

红米手机Redmi 不会自动弹出USB调试选项,如何处理?(红米小米均适用)

参考&#xff1a; 红米手机Redmi 不会自动弹出USB调试选项&#xff0c;如何处理&#xff1f;&#xff08;红米小米均适用&#xff09; - 知乎 以红米9A为例&#xff1b; 【设置】菜单进入后&#xff0c;找到【我的设备】&#xff0c; 选择【全部参数】&#xff0c; 对准miui版…

npm ERR! code CERT_HAS_EXPIRED 淘宝镜像失效

近期vue安装失败&#xff0c;具体如下&#xff1a; 1.先npm cache clean --force 再下载 插件后缀加上 --legacy-peer-deps 2.certificate has expired npm ERR! code CERT_HAS_EXPIRED npm ERR! errno CERT_HAS_EXPIRED npm ERR! request to https://registry.npm.taobao.o…