多线程(八):常见锁策略

目录

前言

1. 乐观锁 VS 悲观锁

乐观锁

悲观锁

2. 轻量级锁 VS 重量级锁

轻量级锁

 3. 自旋锁 VS 挂起等待锁

自旋锁

挂起等待锁

4. 读写锁 VS 互斥锁

5. 可重入锁 vs 不可重入锁

死锁

发生死锁的情况

死锁产生的四个必要条件如下:

6. 公平锁和非公平锁

公平锁 

非公平所

synchronized (只考虑 JDK 1.8)

CAS

基于 CAS的操作:

1. 实现原子类

2. 实现自旋锁


前言

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

下面我会使用很多插图来更好的了解。

接下来的插图皆来自于网络。

1. 乐观锁 VS 悲观锁

锁的实现者,预测接下来发生的冲突概率大不大?根据这个冲突的概率,由实现者来决定接下来该咋办:

乐观锁

乐观锁,顾名思义,乐观豁达;

我们假设数据一般情况下不会产生并发冲突,所以在数据进行提交更新的时候(读多写少),才会正式对数据是否产生并发冲突进行检测,如果发现并发冲突了,则让返回用户错误的信息,让用户决定如何去做。

悲观锁

相比于乐观锁,悲观锁是一种非常悲观的思想,遇到事总是想到最坏的情况,认为写多读少,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁

2. 轻量级锁 VS 重量级锁

轻重量级区别在于效率,效率高那么就认为是轻量级,效率低那么就认为是重量级:

轻量级锁

这里的 CAS 操作,我们后面就会介绍到。

“轻量级”是相对于使用操作系统互斥量来实现的传统锁而言的。
轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用产生的性能消耗。
轻量级锁所适应的场景是线程交替执行同步块的情况,如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。

加锁机制重度依赖了 OS 提供了 mutex

  • 大量的内核态用户态切换
  • 很容易引发线程的调度
     

这两个操作, 成本比较高. 一旦涉及到用户态和内核态的切换, 就意味着 "沧海桑田"

 3. 自旋锁 VS 挂起等待锁

自旋锁

自旋锁是轻量级锁的一种具体表现;

这通常是用户态的,不经过内核态,所以消耗相对时间较短;

举个栗子🌰🌰

这是我们老师上课所举的:

他呢跟他女神表白,但是被女神发了好人卡(加锁失败),但是失败之后怎么办呢?

自旋锁:每天锲而不舍,向女神发早安晚安,终于有一天,女神和她男朋友分手了,这是机会就来了;第一时间就有机会枪到锁,然后加锁。

优点:一旦锁被释放,那么可以第一时间抢到锁。

缺点:但是每天围绕着女神转,啥也不干,那么就会造成资源的浪费(忙等,照成CPU 资源的浪费)。

挂起等待锁

还是拿上面的栗子:

一旦被拒绝,那么就不理女神了,潜心敲代码,学习;终于有一天,女神分手了,想起了他来,主动要求加锁;

挂起等待锁表示当获取锁失败之后,对应的线程就要在内核中挂起等待(放弃CPU,进入等待队列),需要在锁被释放之后由操作系统唤醒;这是典型的重量级锁的栗子。

优点:减少了CPU 资源的浪费

缺点:不能第一时间抢到锁,什么时候能加锁,由系统决定

挂起等待锁与自旋锁的区别

  • 最明显的区别就是,挂起等待锁开销比自旋锁要大,且挂起等待锁效率不如自旋锁。
  • 挂起等待锁会放弃CPU资源,自旋锁不会放弃CPU资源,会一直等到锁释放为止。
  • 自旋锁相较于挂起等待锁更能及时获取到刚释放的锁。
  • 自旋锁相较于挂起等待锁的劣势就是当自旋的时间长了,会持续地销耗CPU资源,因此自旋锁也可以说是乐观锁。

针对上述三组策略,我们synchronized 属于哪种锁呢?

synchronized 即属于乐观锁又属于悲观锁,既是轻量级锁又是重量级锁,既是自旋锁又是挂起等待锁。

synchronized 会根据锁竞争的激烈程度,自己适应。

竞争激烈程度高,那么就是一个悲观锁,以重量级锁的状态运行。

竞争激烈程度低,那么就是一个乐观锁,以轻量级锁的状态运行。

4. 读写锁 VS 互斥锁

我们的synchronized 就是个典型的互斥锁;加锁就是单纯的加锁,没有更细的划分了

像synchronized 只有两步操作:

  1. 进入代码块,加锁
  2. 除了代码块,解锁

多线程之间,数据的读取方之间不会产生线程安全问题,但数据的写入方互相之间以及和读者之间都需要进行互斥。如果两种场景下都用同一个锁,就会产生极大的性能损耗。所以读写锁因此而产生。
读写锁(readers-writer lock),看英文可以顾名思义,在执行加锁操作时需要额外表明读写意图,复数读者之间并不互斥,而写者则要求与任何人互斥。

互斥锁用最简单的一句话来理解:某个资源只能被一个线程访问,读读,读写,写读,写写都是一样的。

下图也很好的演示了读写锁和互斥锁的特性:

读写锁是通过ReentrantReadWriteLock这个类来实现,在JAVA里面,为了提高性能而提供了这么个东西,读的地方用读锁,写的地方用写锁,读锁并不互斥,读写互斥,这部分直接由JVM进行控制。

来看代码(不要求会,下次需要用直接查):


// 创建一个读写锁
private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
// 获取读锁
rwLock.readLock().lock();
// 释放读锁
rwLock.readLock().unlock();
// 创建一个写锁
rwLock.writeLock().lock();
// 写锁 释放
rwLock.writeLock().unlock();

5. 可重入锁 vs 不可重入锁

可重入锁(递归锁),指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码;但不受影响 在 JAVA 环境下 ReentrantLock 和 synchronized 都是 可重入锁

举个不恰当的栗子:

 滑稽老铁在上厕所,突然时空错乱,滑稽老铁跑到外面来了,但是厕所门还是锁着的,这就是不可重入锁也叫做死锁。如果还是可以进去那么就是可重入锁。

什么叫死锁?

死锁

就是多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。

我们来写个伪代码:

Object locker = new Object();
sychronized(loker) {
   ......//中途有很多代码
   ......
   sychronized(loker) {
  
   }
}

那么此时,我们就发生了死锁

我们要想进入第二个锁那么就需要先第一个锁释放;

我们要想进入第一个锁那么就需要先第二个锁释放。

逻辑上矛盾了 --> 死锁。

发生死锁的情况

1. 一个线程一把锁,例如上述的情况;可重入锁没事,但不可重入锁会死锁

2. 两个线程两把锁,即使可冲突也可以发生死锁的情况。例如:

死锁产生的四个必要条件如下:

  1. 互斥条件:一个资源同一时间能且只能被一个线程访问;
  2. 不可掠夺:当资源被一个线程占用时,其他线程不可抢夺该资源;
  3. 请求与等待:当资源被一个线程占用时,其他线程只能等待资源的释放再拥有;
  4. 指的是若干线程形成头尾相接的情况,将所有资源都占用导致的整体死锁或局部死锁。

既然了解了死锁的必要条件,那么我们只要破坏其中一个条件则可避免产生死锁。

通常我们在业务中,可以设置等待时间。例如尝试占用资源时,设置等待时间,时间内未获得资源,则放弃尝试,避免程序长时间等待,占用过高的CPU资源。

尽量一次只占用一个资源,不要一次嵌套的占用多个资源,占用资源链越长,越容易产生死锁问题。

6. 公平锁和非公平锁

我们认为在我们追求女神的时候,每个追求者都有相同的概率追求到,我们认为这样的叫做公平锁;女神有偏爱,可能会对某个追求者有好感,我们认为是非公平锁。

但是在计算机看来刚刚相反,先到先得才是公平锁,等概率事件才是非公平锁。

公平锁 

我们来看看图:

公平锁是一种设计思想,多线程在进行数据请求的过程中,先去队列中申请锁,按照FIFO先进先出的原则拿到线程,然后占有锁。

非公平所

既然有公平锁,那就有非公平锁,也是一种设计思想。线程尝试获取锁,如果获取不到,这时候采用公平锁的方式进行,与此同时,多个线程获取锁的顺序有一定的随机性,并非按照先到先得的方式进行。

优点:性能上高于公平锁

缺点:存在线程饥饿问题,存在某一个线程一直获取不到锁导致一直等待,“饿死了”

synchronized (只考虑 JDK 1.8)

  1.  开始时是乐观锁, 如果锁冲突频繁, 就转换为悲观锁.
  2.  开始是轻量级锁实现, 如果锁被持有的时间较长, 就转换成重量级锁.
  3.  实现轻量级锁的时候大概率用到的自旋锁策略
  4.  是一种不公平锁
  5.  是一种可重入锁
  6.  不是读写锁

CAS

什么是CAS ,CAS 全称叫做 compare and swap (比较与交换)

我们假设内存中的原数据V,旧的预期值A,需要修改的新值B。
1. 比较 A 与 V 是否相等。(比较)
2. 如果比较相等,将 B 写入 V。(交换)
3. 返回操作是否成功

下面写的代码不是原子的, 真实的 CAS 是一个原子的硬件指令完成的. 这个伪代码只是辅助理解
CAS 的工作流程.

寄存器A 的值与 内存地址中的值相比,如果相等,就将寄存器B 的值与内存地址中的值进行交换。

CAS 是一个原子的硬件指令,仅靠硬件支持,并非是段代码,那么这就打开了新世界的大门,我们可以 不需要通过多线程也可以保证线程安全问题。

基于 CAS的操作:

1. 实现原子类

还记得我们最开始将多线程的案例吗?

两个线程各自增加 5W 次,结果是小于 10W的。

我们来看看通过CAS操作,如何实习:

代码:

结果:

线程是安全的。 

我们内置了一个 AtomicInteger 这个原子类,这个类保证了 ++ 和 -- 的线程安全。

我们来看看getAndInteger 方法:

  •  如果发现 value 值和 oldValue 值相等,那么就将 oldValue +1 赋给 value,那么就相当于 ++ 了一次;返回 true。
  • 反之,如果  value 值和 oldValue 值不相等,那么返回 false ,继续循环。

在多线程的案例下,是可能发生 上述两种情况的;但是我们 CAS 反复观察它的值是否发生了变化,没变化就自增,没变化过就先更新在自增。

我们之前的案例为什么 线程不安全呢?就是 一个线程无法及时感应到 另一个线程的变化!

2. 实现自旋锁

来看代码:

如果我们现在的 owner 为 null 那么就将 当前的引用设置到 owner 中,完成加锁操作。

否则,owner 不为 null ,那么就飞快地进行循环,反复的询问,锁是否被释放,一旦被释放了,那么就将第一时间拿到这个锁。

好处就是能第一时间拿到锁;

坏处就是消耗CPU 资源忙等; 

我们一般是在乐观锁的情况下使用;此时锁竞争程度不激烈。

还有一个经典的面试题:

ABA 问题:

假设存在两个线程 t1 和 t2. 有一个共享变量 num, 初始值为 A.
接下来, 线程 t1 想使用 CAS 把 num 值改成 Z, 那么就需要
1. 先读取 num 的值, 记录到 oldNum 变量中.
2. 使用 CAS 判定当前 num 的值是否为 A, 如果为 A, 就修改成 Z.
但是, 在 t1 执行这两个操作之间, t2 线程可能把 num 的值从 A 改成了 B, 又从 B 改成了 A

给要修改的值, 引入版本号. 在 CAS 比较数据当前值和旧值的同时, 也要比较版本号是否符合预期.
CAS 操作在读取旧值的同时, 也要读取版本号.
真正修改的时候:

  1. 如果当前版本号和读到的版本号相同, 则修改数据, 并把版本号 + 1.
  2. 如果当前版本号高于读到的版本号. 就操作失败(认为数据已经被修改过了).

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Java EE】-多线程编程(九) 锁策略CAS锁优化

作者:学Java的冬瓜 博客主页:☀冬瓜的主页🌙 专栏:【JavaEE】 分享: 主要内容:乐观锁VS悲观锁、轻量级锁VS重量级锁、自旋锁VS挂起等待锁、互斥锁VS读写锁、公平锁VS非公平锁、可重入锁VS不可重入锁。CAS实…

Python数据结构与算法-树

一、树的概念详情见 https://blog.csdn.net/little_limin/article/details/129845592 Python数据结构与算法-堆排序(NB组)—— 一、树的基础知识二、树的实例:模拟文件系统1、树的存储树结构也是链式存储的,与链表的结构相似&…

类ChatGPT代码级解读:如何从零起步实现Transformer、llama/ChatGLM

前言 最近一直在做类ChatGPT项目的部署 微调,关注比较多的是两个:一个LLaMA,一个ChatGLM,会发现有不少模型是基于这两个模型去做微调的,说到微调,那具体怎么微调呢,因此又详细了解了一下微调代…

Vulnhub_Pylington

目录 一、信息收集 (一)端口服务探测 (二)目录扫描 二、漏洞挖掘 (一)robots敏感信息泄露 (二)python IDE沙箱绕过RCE 1. python敏感函数沙盒绕过 2. exec(__import_…

【ES】搜索结果处理RestClient查询文档

【ES】搜索结果处理&RestClient查询文档2.搜索结果处理2.1.排序2.1.1.普通字段排序2.1.2.地理坐标排序2.2.分页2.2.1.基本的分页2.2.2.深度分页问题2.2.3.小结2.3.高亮2.3.1.高亮原理2.3.2.实现高亮2.4.总结3.RestClient查询文档3.1.快速入门3.1.1.发起查询请求3.1.2.解析响…

Python做个猫狗识别系统,给人美心善的邻居

嗨害大家好鸭!我是爱摸鱼的芝士❤ 宠物真的看着好治愈 谁不想有一只属于自己的乖乖宠物捏~ 这篇文章中我放弃了以往的model.fit()训练方法, 改用model.train_on_batch方法。 两种方法的比较: model.fit():用起来十分简单&#…

Kubernetes 部署 StarRocks 集群

文章目录StarRocks简介系统架构图安装部署StarRocks手动部署通过 Docker部署使用 StarGo 部署管理通过 StarRocks Manager部署管理通过 Kubernetes部署工作原理逻辑图部署 StarRocks Operator部署 StarRocks 集群访问 StarRocks 集群集群内访问 StarRocks 集群集群外访问 StarR…

【案例实践】R语言多元数据统计分析在生态环境中的实践应用

查看原文>>>R语言生物群落分析绘图、多元统计分析、CMIP6、遥感碳储量、GEE林业、InVEST等 生态环境领域研究中常常面对众多的不同类型的数据或变量,当要同时分析多个因变量(y)时需要用到多元统计分析(multivariate sta…

Spark----DataFrame和DataSet

Spark之DataFrame和DataSet 文章目录Spark之DataFrame和DataSetDataFrameDSL 语法创建DataFrame查看DataFrame的Schema信息只查看列数据的6种方式按照“age”分区,查看数据条数增加列withColumn修改列名withColumnRenamedRDD 转换为 DataFrameDataFrame 转换为 RDD转…

音质蓝牙耳机哪款好用?2023公认音质好的四款蓝牙耳机推荐

现如今,蓝牙耳机越来越受欢迎,不少人在听歌、追剧、甚至是玩游戏的时候都会戴着它。最近看到很多人问,音质蓝牙耳机哪款好用?针对这个问题,我来给大家推荐四款公认音质好的蓝牙耳机,一起来看看吧。 一、南…

算法笔记:Frechet距离度量

曲线之间相似性的度量,它考虑了沿曲线的点的位置和顺序 1 概念 1.1 直观理解 主人走路径A,狗走路径B,他们有不同的配速方案主人和狗各自走完这两条路径过程中所需要的最短狗绳长度 (在某一种配速下需要的狗绳长度)&a…

考研复试确认神操作!

终于进行到了研究生考试的尾声,但让考生感到无力吐槽的事情,却还在继续上演,比如苏科大,再比如中地大、苏大,三所学校的神操作,着实让无数考生忍不住调侃:原来考研不仅拼实力,还得拼…

你的APP内存还在暴增吗?试着用Bitmap管理下内存~

作者:layz4android 相信伙伴们在日常的开发中,一定对图片加载有所涉猎,而且对于图片加载现有的第三方库也很多,例如Glide、coil等,使用这些三方库我们好像就没有啥担忧的,他们内部的内存管理和缓存策略做的…

如何实现Chatgpt写文章(附chatgpt3.5免费接口)

申明:本次只是说一下实现思路,官方的接口以及如何实现方式,本文没有提及,这次只是一个思路,若想代替人工完成质量还差的很远,请审核大大放行 今天再次优化了代码,修复了一些bug,考虑…

VUE 学习笔记(一)开发环境搭建

1、Visual Studio Code安装及使用 下载地址官网:https://code.visualstudio.com/ 直接点击下载按钮即可,会根据系统自动下载合适的版本,无需自行选择。 2、VSCode 上安装:JavaScript Debugger 目前 Debugger for Chrome 已经处…

使用向量机(SVM)算法的推荐系统部署实现

包括3个模块:数据预处理、模型训练及保存、模型测试,下面分别给出各模块的功能介绍及相关代码。 数据集下载链接为https://www.aitechclub.com/data-detail? data_id29,停用词典下载链接为http://www.datasoldier.net/archives/636。 1.数…

232:vue+openlayers选择左右两部分的地图,不重复,横向卷帘

第232个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+openlayers项目中自定义js实现横向卷帘。这个示例中从左右两个选择框中来选择不同的地图,做了不重复的处理,即同一个数组,两部分根据选择后的状态做disabled处理,避免重复选择。 直接复制下面的 vue+openlayers…

c语言—指针进阶

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; 给大家跳段街舞感谢支持&#xff01;ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ…

第13届蓝桥杯省赛真题剖析-2022年4月17日Scratch编程初中级组

[导读]&#xff1a;超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成&#xff0c;后续会不定期解读蓝桥杯真题&#xff0c;这是Scratch蓝桥杯真题解析第122讲。 第13届蓝桥杯省赛举办了两次&#xff0c;这是2022年4月17日举行的第一次省赛&#xff0c;比赛仍然采取线上形…

ChatGPT技术原理、研究框架,应用实践及发展趋势(附166份报告)

​ 一、AI框架重要性日益突显&#xff0c;框架技术发展进入繁荣期&#xff0c;国内AI框架技术加速发展&#xff1a; 1、AI框架作为衔接数据和模型的重要桥梁&#xff0c;发展进入繁荣期&#xff0c;国内外框架功能及性能加速迭代&#xff1b; 2、Pytorch、Tensorflow占据AI框…