当前位置: 首页 > news >正文

常见锁策略

目录

1.乐观锁/悲观锁

2.重量级锁/轻量级锁(轻量重量是站在加锁开销的角度)

3.挂起等待锁/自旋锁

4.公平锁/非公平锁

5.可重入锁与不可重入锁

6.读写锁

synchronized

面试题:是什么偏向锁?

锁的升级:

锁消除:

锁粗化:编译器的优化策略

关于锁的一些面试题

CAS

2.CAS实现自旋锁

3.CAS的ABA问题,版本号的引入

4.相关面试题CAS

7.Callable接口

8.ReentrantLock 可重入互斥锁

ReentrantLock 和 synchronized 的区别

举例:​编辑

如何更好的选择使用synchronized / ReentrantLock锁?

面试题:

9.信号量Semaphore

10.CountDownLatch 同时等待N个任务结束——锁存器


1.乐观锁/悲观锁

这两个词不是指某个锁,而是指某类锁是锁的特点

乐观锁:加锁的时候,假设锁的冲突概率不大 ——> 接下来围绕锁要做的工作就会更少。一般情况下不会加锁,只有在数据提交更新的时候才会对数据进行并发冲突检测,如果发现并发冲突了就会返回给用户信息,让用户决定如何处置。

悲观锁:加锁的时候,假设锁冲突的概率很大 ——> 接下来围绕锁要做的工作很多。总是假设最坏的情况,每次在取数据时都以为数据会修改,所以每次访问数据的时候都会加锁。

乐观锁与悲观锁,背后要做的事情是不同的。

synchronized 这把锁算是乐观/悲观锁?

这是自适应锁,初始情况下是乐观锁(预估接下来的冲突概率不大),当发现锁竞争比较频繁的时候就会自动切换为悲观锁。

在背后会默默统计冲突的次数,达到一定次数就会转变为悲观锁。


2.重量级锁/轻量级锁(轻量重量是站在加锁开销的角度)

重量级锁:加锁的开销比较大,要做的工作往往更多。

会有大量的用户态内核态的代码切换。很容易引发线程调度。

轻量级锁:加锁的开销比较小,要做的工作就较少。

尽量在用户态的代码完成,实在不行再调用内核态。不太容易引发线程调度。

虽然这两个锁的效果与乐观锁/悲观锁是重叠的,站在的角度不一样。

往往悲观锁要做的工作更多,乐观锁要做的工作更少。

在重量级锁/轻量级锁之间,synchronized也是自适应的。冲突比较严重会变成重量级锁。

不能100%认为 重量级锁与悲观锁等价,轻量级锁与乐观锁等价。


3.挂起等待锁/自旋锁

挂起等待锁是悲观锁/重量级锁的一种典型表现。

自旋锁就属于是乐观锁/轻量级锁的一种典型是实现。

按照之前的方式,线程在抢锁失败后就会进入阻塞放弃cpu资源,等待被再次调用。采用自旋锁就可以在没有抢到锁后一直尝试获取锁,直到锁释放后第一时间获取锁。

而挂起等待锁是先让出cpu资源,再去做些别的事情,等到锁被释放再尝试去加锁。

synchronized是自适应的,一开始自旋锁,等到一段时间还没有获取到锁,就变为挂起等待锁。

轻量级锁是基于 自旋 的方式在jvm内部用户态的代码实现的。

重量级锁是基于 挂起等待 的方式实现的,调用操作系统api,内核中实现的。


4.公平锁/非公平锁

假如说女神分手了,但是追她的人有一大堆,接下来的的人如果是按照先来后到的方式这就是公平锁。如果这些人不按照先来后到,按概率均等的方式就是非公平锁

操作系统内部线程调度是随机的,不做任何额外的限制,锁就是非公平锁。

如果想要实现公平锁就要依赖额外的数据结构,来记录线程的顺序

锁的公平与非公平没有好坏之分,要结合具体的场景。


5.可重入锁与不可重入锁

可重入锁:允许一个线程多次获取同一把锁。

不可重入锁:只允许加锁一次的线程。

死锁问题:

如果一个线程针对同一把锁连续枷锁两次,就有可能出现死锁,如果是可重入锁,就可以避免死锁。

6.读写锁

所谓的读写锁 就是把读写操作分为两个情况。

多线程之间,数据的读取不会产生线程安全问题,但是数据的写入会有这种问题。

1.两个数据都是读操作,直接并发读即可

2.两个数据都要写,有线程安全问题

3.一个线程读,一个线程写,有线程安全问题。

读写锁就是把读操作和写操作区别对待,java标准库中提供了ReentrantReadWriteLock.ReadLock

类 表示读锁。这个对象提供了lock/unlock方法

ReentrantReadWriteLock.WriteLock 类表示写锁,这个对象也提供了lock/unlock进行加锁解锁

synchronized不是读写锁。


synchronized

1.乐观悲观,自适应

2.重量轻量,自适应

3.自旋挂起等待,自适应

4.非公平锁

5.可重入锁

6.不是读写锁

面试题:是什么偏向锁?

不是真正的加锁(真正的加锁开销比较大),偏向锁只是做一个标记,(标记的过程非常轻量高效)。如果出现锁竞争,再取消偏向锁的状态,进入轻量锁。本质上是推迟了加锁的时机,是懒汉思想的体现。


锁的升级:

ynchronized的加锁过程:刚开始使用时,会处于一个“偏向锁的状态”遇到线程之间的锁竞争,就会升级到“轻量级锁” 进一步统计竞争出现的频次,达到一定层次之后就会升级到“重量级锁”就是为了能够让synchronized很好的使用不同的场景。降低程序员的使用负担。

对于jvm来说,锁的升级过程是不可逆的。

锁消除:

在编写代码时,编译器会对加上的synchronized进行判断合不合适,如果没有必要就会把这个所给优化掉。避免了无脑加锁。

锁粗化:编译器的优化策略

锁的粒度:就是粗和细,如果一段逻辑中频发出现加锁解锁,编译器+jvm会自动进行锁的组化。

开发时使用细粒度的锁就是期望释放锁的时候其他线程能够及时获取锁,但实际上如果没有别的线程来抢占,这种情况jvm就会自动把锁粗化,避免频繁申请释放锁。


关于锁的一些面试题

1.介绍一下读写锁?

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

读锁 与 读锁之间不互斥;

读锁 与 写锁之间互斥;

写锁 与 写锁之间互斥;

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

如果获取锁失败,立即尝试获取锁,无限循环,知道获取到锁为止,第一次获取锁失败紧接着第二次尝试就会到来,一旦锁被释放,就能第一时间获取到锁。

相比于挂起等待锁,优点:一旦锁被释放就能第一时间获取到锁,更加高效,在锁的持有时间较短的场景下非常有用。

缺点:如果所得持有时间较长,就会非常浪费cpu资源。

3.synchronized是可重入锁吗?

是的;

可重入锁就是连续加几次锁不会导致死锁;

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


CAS

比较内存与寄存器中的内容,如果发现相同,就将另一个寄存器的值赋值给内存。

说明:

一个内存数据 和 寄存器1,寄存器2.

比较内存与寄存器1的值,如果不相等就不操作

如果相等:就将寄存器2的值赋值给内存,

CAS的使用场景:

1.基于CAS实现”原子类“

在多线程中int / long 等这些定义的变量的赋值中,赋值操作++,-- 都不是原子的

通过对 int long 这些类型的封装,从而可以实现原子的操作。

由上图可见,在多线程中并没有加锁,但是并没有发生线程安全问题,因为这个基于CAS的对基本类型进行封装的方法进行运算时,可以看作是一个指令·完成的。

这些都是对基本数据类型的封装。

CAS实现原子类的原理?


2.CAS实现自旋锁


3.CAS的ABA问题,版本号的引入

CAS确实好用,但是会有ABA问题。因为CAS在比较的过程中会判断有没有别的线程插入进来执行

而CAS判断是否有这一结果的依据是判断值有没有被修改,如果值相同,就认为没有别修改如果值不同就认为有线程插入修改了但是值的相等不相等并不等同于线程有没有插入执行。

通常情况下ABA问题不会带来bug,但是有一个极端的例子就是转账

因为余额既能加,又能减。所以会发生ABA问题,如果只能加不能减,或者只能减少就不会发生ABA问题。于是引入一个新的概念“版本号”,这就是一个只能增加的整数。

4.相关面试题CAS

1.请你说一下自己理解的CAS?

全称Compare and swap 即:“比较并交换”,相当于一个原子操作,同时完成”读取内存,比较是否相等,修改内存“这三个步骤。也可以是实现模拟自旋锁。

2.ABA问题怎么解决?

给要修改的数据引入版本号,即比较并交换当前值和就值得同时,也要比较版本号是否符合预期,如果版本号与预期一致就真正修改,并且让版本号自增。如果发现当前版本号比之前的大就修改失败。


7.Callable接口

1.Callable和Runnable 相对都是描述一个任务,Callable是描述带有返回值的任务,Runnable描述的是不带返回值的任务。

2.Callable通常需要搭配FutureTask使用,FutureTask用来保存Callable的返回结果。因为Callbale是在另一个线程里执行的,啥时候执行结束不确定,TutureTask就负责等待结果出来的工作。

使用Callable计算线程1+2+3+.......+1000

理解Callbale :

1.创建一个匿名内部类,实现Callble接口,Callble带有泛型参数,泛型参数表示返回值类型。

2.重写Callable的call方法,完成累加的过程,直接通过返回值返回计算结果。

3.把Callable实例的call用FtureTask包装一下

4.创建线程,把FutureTask的实例传入Thread,线程就会执行FutureTask内部的Callable的call方法

5.在主线程中调用futureTask.get()放法获取返回值。futureTask能够阻塞等待新线程执行完毕。

线程的创建方式

1.直接继承Thread

2.使用Runnable

3.使用Callable

4.使用lambda

5.使用线程池


8.ReentrantLock 可重入互斥锁

通过lock/unlock方法来加锁减锁。

trylock(超时间):加锁,如果取不到锁等待一段时间就放弃等待

ReentrantLock lock = new ReentrantLock();lock.lock();try{} finally{lock.unlock();
}
ReentrantLock 和 synchronized 的区别

1.synchronized()是关键字,底层是jvm的c++代码实现的

    ReentrantLock 是标准库提供的类,是java代码实现的。

2.synchronized通过控制代码块加锁解锁,ReentrantLock通过lock/unlock加锁减锁。

3.ReetrantLock 提供啦tryLock这样的方法,在加锁的时候不会阻塞,而是等待一段时间后直接返回,通过返回值来反馈加锁成功还是失败。

4.synchronized是公平锁。ReentrantLock是默认非公平锁。也可以实现公平锁。

5.ReentrantLock还提供啦“等待通知机制” 基于Condition类,能力比wait/notify更强一些。

举例:
如何更好的选择使用synchronized / ReentrantLock锁?

1.锁竞争不激烈的时候使用synchronized,效率更高,自动释放。更加方便

2.锁竞争不激烈的时候使用ReentrantLock,搭配tryLock 更加灵活的控制锁的行为,不是死等。

3.如果需要使用公平锁,使用ReentrantLock。

面试题:

1.线程同步的方式有哪些?

synchronized,ReentrantLock,Semaphore等都可以用于线程同步。

2.为什么有了synchronized还需要juc的lock?

ReentrantLock使用时需要手动释放,使用更加灵活。

synchronized申请失败时会死等,ReentrantLocck可以通过tryLock的方式等待一段时间后就放弃。

ReentrantLock默认是非公平锁,可以通过构造方法传入true开始公平锁

synchronized是通过Object的wait/notify实现等待,每次唤醒一个随即等待的线程。ReentrantLock搭配Condition实现等待与唤醒可以更加精确唤醒指定线程。        


9.信号量Semaphore

信号量相等于一个计数器,通过计数器的衡量可用资源的个数。

申请资源 计数器+1,释放资源 计数器-1;这些操作是原子的可以在多线程环境下使用

值为1的计数器相当于“锁”。

举例:


10.CountDownLatch 同时等待N个任务结束——锁存器

好比跑步比赛,只有所有选手都过了终点才会公布成绩。

1.构造CountDownLatch实例,初话化10表示有10个任务

2.每个任务执行完毕都调用latch.countDown(),在CountDownLatch内部的计数器同时自减

3,主线程使用latch.await(),阻塞等待所有任务都执行完毕,相当于计数器为0了


11.多线程环境下使用哈希表

1.Hashtable:

只是简单地把关键方法加上关键字;

 

这相当于直接对Hashtable对象本身加锁。

1.如果多个线程访问同一个Hashtable会频繁出发锁冲突。因为每个方法几乎都有synchronized,任何一个操作都会触发锁竞争。

2.size属性也是通过synchronized来控制同步的,所以更新的也是比较慢的。

3.一旦出发扩容,就由该线程完成完成整个扩容,创建新的hash表,再把所有元素搬进去。这样非常耗时,这一系列操作可能是一次put就完成,使得这次put的开销非常之大。

2.ConcurrentHashMap

相比于Hashtable做出了一系列的改进与优化。以java1.8为例

1.读操作没有加锁而是使用关键字volatile保证从内存读取结果正确,只对写操作进行加锁,加锁的方式不是用synchronized也不是整个对象而是通过“锁桶”(以每个链表的头节点作为锁对象),大大降低了所冲突的概率。

2.引入CAS原子操作,针对修改size这个样的操作,借助CAS完成并不会加锁。

3.针对Hash表的扩容进行了特殊的优化,ConcurrentHashMap进行“化整为零”,不会在一次操作中就把所有的数据搬运,而是一次只搬运一部分。此后每次操作都会触发一部分的key搬运,最终把key搬运完成。(这里的扩容机制需要用到B+树,查询的开销十分稳定)

当新表旧表同时存在时,插入操作会插入到新的空间;

查询/修改/删除都是需要新表与旧表都要查询的。

网上资料有”分段锁“说法,这其实与ConcurrentHashMap早期的思想方式一致,只不过是一个锁要管理好几个链表,这种实现方式处理冲突的做的还不彻底,分段锁的实现方式也更复杂。


相关文章:

常见锁策略

目录 1.乐观锁/悲观锁 2.重量级锁/轻量级锁(轻量重量是站在加锁开销的角度) 3.挂起等待锁/自旋锁 4.公平锁/非公平锁 5.可重入锁与不可重入锁 6.读写锁 synchronized 面试题:是什么偏向锁? 锁的升级: 锁消除&…...

【机器学习】人工神经网络优化方法及正则化技术

🌈个人主页: 鑫宝Code 🔥热门专栏: 闲话杂谈| 炫酷HTML | JavaScript基础 ​💫个人格言: "如无必要,勿增实体" 文章目录 人工神经网络优化方法及正则化技术1. 引言2. 神经网络优化的基础2.1 损失函数2.…...

Django异步请求和后台管理实战

项目概述 项目实现Ajax异步请求局部刷新使用XAdmin后台模板提供图片上传接口在明细页应用了富文本编辑器在加载图书信息的时候使用LazyLoad(图片懒加载) # 环境 asgiref3.7.2 crispy-bootstrap32024.1 defusedxml0.7.1 diff-match-patch20230430 Djang…...

大奖放送 | AI编程达人秀视频文章征集大赛来啦!

AI Coding,可以有多少种打开玩法?腾讯云AI代码助手是一款辅助编码工具,基于混元大模型,提供技术对话、代码补全、代码诊断和优化等能力,为你生成优质代码,帮你解决技术难题,提升编码效率。 我…...

最新小猫咪PHP加密系统源码V1.4_本地API接口_带后台

小猫咪PHP加密系统历时半年,它再一次迎来更新,更新加密算法(这应该是最后一次更新加密算法了,以后主要更新都在框架功能上面了),适配php56-php74,取消批量加密(一些不可控因素&#…...

a bag of bones

以下是根据你提供的内容制作的5道选择题,包括答案和解析: 1. 短语 "a bag of bones" 通常用来描述什么? - A. 一个恐怖片中的角色 - B. 一个非常瘦弱的人 - C. 一个懒惰的人 - D. 一个穿着比基尼的人 答案:B 解析&#…...

XLT高速线缆自动化测试系统

高速线缆自动化测试系统 随着高速通信的快速发展,对于高速数据通信线缆性能要求日益增高,在其硏发、生产阶段,需要多次测试射频性能。传统人工手动测试存在测试环境搭建复杂、测试效率低、耗时长,特别是多次测试中因为人工测试带…...

微软AI业务最新营收数据情况(2024年7月)

Azure AI 年度经常性收入 (ARR):达到50亿美元客户数量:60,000家平均客户价值 (ACV) 中位数:83,000美元同比增长率:达到了惊人的900% GitHub Copilot 年度经常性收入 (ARR):达到3亿美元客户数量:77,000家…...

canvas绘制表格

canvas绘制表格 最近在为公司产品做技术预研,经理让用canvas做一个表格,于是就有了这篇博客。 我们的数据是后端通过MQTT推送过来的 我在代码中也直接使用了 具体MQTT的实现代码,可见博客 在vue使用MQTT 在这里为了方便实用我直接封装成组件…...

避免溃坝的关键:渗压计在防洪管理中的作用

防洪管理对于保障人民生命财产安全具有重要意义,而溃坝作为防洪管理中的重大风险之一,其防范工作尤为关键。在防洪管理体系中,渗压计作为一种重要的监测工具,发挥着不可替代的作用。本文将深入探讨渗压计在防洪管理中的作用。 实时…...

品牌建设如何助力中小企业突破生存瓶颈?

品牌,不仅仅是一个标志或商标,更是企业的形象、声誉和信誉的体现。品牌的存在是为了使企业区别于其他竞争对手,树立独特的形象,赢得消费者的认可和信任。 品牌的本质是品牌拥有者的产品、服务或其它优于竞争对手的优势能为目标受…...

探索Python FastAPI的Annotated参数设计:提升代码的灵活性与可读性

在现代软件开发中,代码的可读性和灵活性是至关重要的。Python的FastAPI框架以其高性能和易用性而受到开发者的喜爱。FastAPI提供了一种名为Annotated的参数设计方式,它允许开发者以类型注解的形式增强函数参数的定义,从而提升代码的表达力和灵…...

ClickHouse 进阶【建表、查询优化】

1、ClickHouse 进阶 因为上一节部署了集群模式,所以需要启动 Zookeeper 和 ck 集群; 1.1、Explain 基本语法 EXPLAIN [AST | SYNTAX | PLAN | PIPELINE] [setting value, ...] SELECT ... [FORMAT ...] AST:用于查看语法树SYNTAX&#…...

Qt拖拽事件详解及代码实现

Qt拖拽事件详解及代码实现 前言项目描述代码结构简介代码详解 前言 qt拖拽事件是一项非常常用并且非常好用的功能,拖拽实际上是一种信息传递的载体,其目的是将信息从一个对象传递给另一个对象。通过拖拽可以简化文件打开或业务操作流程,qt初…...

云原生的候选应用

提示 该内容摘自电子书《为 Azure 构建云原生 .NET 应用程序》,可在**.NET Docs**上获取,也可以免费下载 PDF并离线阅读。 考虑一下您的组织需要构建哪些应用程序。然后,看看您投资组合中的现有应用程序。其中有多少需要云原生架构&#xff…...

什么是单例模式?

单例模式是一种常见的设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取这个唯一实例。这种模式通常在需要控制某些资源的访问权限或确保对象的唯一性时使用。 单例模式的特点 唯一实例:单例模式确保一个类只有一个实例存在,全局可访问。 延迟实例化:在需…...

F4Pan百度网盘不限速直链解析工具最新可用

最新可用百度网盘不限速直链解析工具,现在很多解析网站和浏览器扩展都失效了,这个是用《F4Pan网盘解析系统开源源码》搭建的,有兴趣可以去研究研究。 下面看一下测试速度超过70MB每秒比开通会员还快非常的恐怖。 使用方法 1.下载F4Pan解析工…...

设计模式实战:智能家居系统的设计与实现

问题描述 设计一个智能家居系统,支持设备的控制(如灯、空调等),提供多种操作策略,并且在设备状态发生变化时通知用户。系统需要确保设备操作的灵活性和可扩展性。 设计分析 命令模式 命令模式用于将请求封装成对象,从而使我们可以用不同的请求、队列或日志来参数化其…...

Unity Rigidbody 踩坑记录

1:两个带有刚体的物体碰撞会一直不停的弹 把被动受力的刚提的 Freeze Position 的勾选 去掉(碰到过一次,有一种受力无法释放又返回给目标的 所以一直弹跳的感觉) 2:子物体 和父物体 都有刚体的情况下 子物体 Freeze R…...

Guitar Pro简谱怎么输入 ?如何把简谱设置到六线谱的下面?

一、Guitar Pro简谱怎么输入 简谱在音乐学习、演奏、创作和传播中都起着非常重要的作用,是音乐领域不可或缺的工具。吉他乐谱的制作可以使简谱,也可以使五线谱、六线谱等多种形式,这几种乐谱都可以使用Guitar Pro来完成。下面来看看Guitar Pr…...

反向工程与模型迁移:打造未来商品详情API的可持续创新体系

在电商行业蓬勃发展的当下,商品详情API作为连接电商平台与开发者、商家及用户的关键纽带,其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息(如名称、价格、库存等)的获取与展示,已难以满足市场对个性化、智能…...

镜像里切换为普通用户

如果你登录远程虚拟机默认就是 root 用户,但你不希望用 root 权限运行 ns-3(这是对的,ns3 工具会拒绝 root),你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案:创建非 roo…...

2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面

代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口(适配服务端返回 Token) export const login async (code, avatar) > {const res await http…...

C# SqlSugar:依赖注入与仓储模式实践

C# SqlSugar:依赖注入与仓储模式实践 在 C# 的应用开发中,数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护,许多开发者会选择成熟的 ORM(对象关系映射)框架,SqlSugar 就是其中备受…...

OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别

OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...

自然语言处理——循环神经网络

自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元(GRU)长短期记忆神经网络(LSTM&#xff09…...

SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)

上一章用到了V2 的概念,其实 Fiori当中还有 V4,咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务),代理中间件(ui5-middleware-simpleproxy)-CSDN博客…...

【Go语言基础【12】】指针:声明、取地址、解引用

文章目录 零、概述:指针 vs. 引用(类比其他语言)一、指针基础概念二、指针声明与初始化三、指针操作符1. &:取地址(拿到内存地址)2. *:解引用(拿到值) 四、空指针&am…...

Python Ovito统计金刚石结构数量

大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...

【JVM】Java虚拟机(二)——垃圾回收

目录 一、如何判断对象可以回收 (一)引用计数法 (二)可达性分析算法 二、垃圾回收算法 (一)标记清除 (二)标记整理 (三)复制 (四&#xff…...