常见的锁策略CAS

目录

一、乐观锁&悲观锁

1.1、悲观锁

1.2、乐观锁

二、重量级锁&轻量级锁

2.1、轻量级锁 

2.2、重量级锁

三、自旋锁&挂机等待锁

3.1、自旋锁 

3.2、挂起等待锁 

四、读写锁&普通互斥锁

4.1、读写锁

4.2、互斥锁 

五、公平锁&非公平锁

六、可重入锁&不可重入锁 

6.1、可重入锁

6.2、不可重入锁

七、对比synchronized

八、CAS

8.1、什么是CAS 

8.2、CAS应用

8.2.1、实现原子类

8.3、CAS的ABA问题


一、乐观锁&悲观锁

1.1、悲观锁

在获取锁的时候预期这个锁的竞争十分激烈,那么就必须先加锁再执行任务,阻塞其它想获取锁的任务。

1.2、乐观锁

在获取锁的时候预期这个锁的竞争不太激烈,那么就可以先不加锁,或者少加锁(有真实的竞争的时候再加锁)

举个栗子:

坤坤要在西安开演唱会,小黑子练习时长两年半好不容易抢到了一张咯咯的演唱会门票去看咯咯的演唱会,就在开场10分钟后,小黑子觉得肚子不舒服想要去厕所方便一下到了厕所这个点没几个人来上厕所,小黑子就没有给厕所门上锁,当他听见有脚步声才把门插上。

二、重量级锁&轻量级锁

锁的核心特性”原子性“,这样的机制追根溯源是CPU这样的硬件设备提供的。

  • CPU提供了"原子操作指令".
  • 操作系统基于CPU的原子指令,实现了mutex互斥锁.
  • JVM基于操作系统提供的互斥锁,实现了synchronized和ReentrantLock等关键字和类.
  • 注意:synchronized并不仅仅是对mutex进行封装,在synchronized内部还做了其它很多工作。

2.1、轻量级锁 

轻量级锁的加锁过程比较简单,用到的资源比较少,典型的就是用户态的一些加锁操作(在java代码层面就可以完成加锁).

  • 少量的内核态用户态切换.
  • 不太容易引发线程调度.

2.2、重量级锁

重量级锁的加锁过程比较复杂,用到的资源也比较多,典型的就是内核态的一些加锁操作.

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

乐观锁是能不加就不加锁,导致消耗的资源也就少了,所以乐观锁也是轻量级锁。

悲观锁是能加就加锁,导致消耗的资源也就多了,所以悲观锁也是重量级锁。

三、自旋锁&挂机等待锁

3.1、自旋锁 

自旋锁伪代码:

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

如果获取锁失败,立即尝试获取锁,无限循环,直到获取锁为止,第一次获取锁失败,第二次的尝试会在极短的时间内到来。所以一旦锁资源被释放,就能够第一时间获取到锁。 

自旋锁是一种典型的轻量级锁实现方式. 

  • 优点:没有放弃CPU,不涉及线程阻塞和调度,一旦锁被释放,就能第一时间获取到锁。
  • 缺点:如果锁被其它线程持有的时间比较久,那么就会持续消耗CPU资源.(而挂起等待锁是不需要消耗CPU的) 

synchronized中的轻量级锁策略大概就是通过自旋锁的方式实现的. 

3.2、挂起等待锁 

不主动访问锁资源,而是让系统调度去竞争锁资源。

  1. 通过阻塞与就绪状态的切换来获取锁资源.
  2. 锁一旦被释放,没有办法立刻知道.
  3. 是通过系统内核来处理的. 

挂起等待锁是一种典型的重量级锁实现方式.

四、读写锁&普通互斥锁

4.1、读写锁

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

读写锁:

在读的时候加读锁(共享锁),多个锁可以共存,同时加多个读锁是互不影响的。

在写的时候加写锁(排他锁),只有一个写锁在执行任务时,和别的锁是冲突的。

  • 写锁和写锁不能共存.
  • 写锁和读锁不能共存.
  • 读锁和读锁可以共存. 

读写锁就是把读操作和写操作区分对待,java库中提供了ReentrantReadWriteLock 类, 实现了读写
锁. 

  • ReentrantReadWriteLock.readLock类表示一个读锁,这个对象提供了lock()/unlock方法进行加锁。
  • ReentrantReadWriteLock.writeLock类表示一个写锁,这个对象也提供了lock()/unlock方法进行加锁。

读写锁特别适合于"频繁读,不频繁写"的场景中.

synchronized不是读写锁 

4.2、互斥锁 

有竞争关系,只能一个线程释放锁之后,别的线程再来抢。 

五、公平锁&非公平锁

5.1、公平锁

公平锁讲究的守规矩,遵循”先来后到“的原则,先排队的线程先获取到锁,后排队的线程后获取到锁。

5.2、非公平锁

大家都去抢锁,谁抢到就是谁的。

注意:

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

synchronized是公平锁 

六、可重入锁&不可重入锁 

6.1、可重入锁

可重入锁可以对一把锁连续加锁多次,而不造成死锁。加锁多次那么解锁也要多次解锁,否则其它线程就无法获取到锁。

java中只要以Reentrant开头的都是可重入锁,而且JDK提供的所有的现成的lock实现类,包括synchronized都是可重入的

6.2、不可重入锁

不可重入锁就是对一把锁连续加锁多次,造成死锁。linux系统提供的mutex是不可重入锁。

七、对比synchronized

八、CAS

8.1、什么是CAS 

CAS:全称Compare and swap,意思就是”比较并交换“,一个CAS涉及到以下操作。

假设内存中的源数据V,旧的预期值是A,需要修改的新值为B。

1、比较A与V是否相等(比较)。

2、如果比较相等,将B写入A(交换)。

3、返回操作是否成功。 

举个栗子:

 场景:

泡了一杯茶,泡上了之后,有事需要出去一趟。

十分钟回来之后,看了一下茶,如果茶还是满杯和走之前是一样的,那么就代表没有人喝过,就可以继续喝。

如果这个茶剩半杯了,就代表有人喝过了,那么就不能喝了,再重新泡一杯。

 

执行CAS操作重要的是确定有没有其它线程修改过第一次获取的值,如果确定没有被修改过,那么当前线程去修改是通过一条CPU指令去完成的,那么修改的过程就是线程安全的。 

8.2、CAS应用

8.2.1、实现原子类

标准库中提供了 java.util.concurrent.atomic 包, 里面的类都是基于这种方式来实现的.
典型的就是 AtomicInteger 类. 其中的 getAndIncrement 相当于 i++ 操作.

AtomicInteger atomicInteger = new AtomicInteger(0);
// 相当于 i++
atomicInteger.getAndIncrement();

假设两个线程同时调用 getAndIncrement
1) 两个线程都读取 value 的值到 oldValue 中. (oldValue 是一个局部变量, 在栈上. 每个线程有自己的栈) 

2) 线程1 先执行 CAS 操作. 由于 oldValue 和 value 的值相同, 直接进行对 value 赋值.
注意:

  •  CAS 是直接读写内存的, 而不是操作寄存器.
  • CAS 的读内存, 比较, 写内存操作是一条硬件指令, 是原子的.

  

3) 线程2 再执行 CAS 操作, 第一次 CAS 的时候发现 oldValue 和 value 不相等, 不能进行赋值. 因此需要进入循环,在循环里重新读取 value 的值赋给 oldValue

4) 线程2 接下来第二次执行 CAS, 此时 oldValue 和 value 相同, 于是直接执行赋值操作.

  

5) 线程1 和 线程2 返回各自的 oldValue 的值即可.

通过形如上述代码就可以实现一个原子类. 不需要使用重量级锁, 就可以高效的完成多线程的自增操作.

示例代码:


import java.util.concurrent.atomic.AtomicInteger;//CAS原子类
public class Exe_01 {public static void main(String[] args) throws InterruptedException {//基于CAS的原子类AtomicInteger atomicInteger=new AtomicInteger();Thread t1=new Thread(() ->{for (int i = 0; i < 50000; i++) {//通过调用并获取自增的方法实现自增操作。atomicInteger.getAndIncrement();}});Thread t2=new Thread(() ->{for (int i = 0; i < 50000; i++) {//通过调用并获取自增的方法实现自增操作。atomicInteger.getAndIncrement();}});//启动线程t1.start();t2.start();//等待两个线程结束t1.join();t2.join();//打印执行结果System.out.println("执行结果——》"+atomicInteger.get());}
}

 

8.3、CAS的ABA问题

刚才的场景:

1、泡好茶出门了,十分钟又回来了,按照之前的逻辑,检查杯子中的水满着没有,如果满着就代表没有人动过,可以继续喝。

2、但是存在一个问题,中途有人喝了一半之后,又续上了,回来一看还是这杯水,这杯水跟走之前那杯水完全不一样了。 

前面看到的结果(A)和后面看到的结果(A)是一样的,但是中间发生过改变(B),只不过这个值跟取值之前是一样的。 

如何解决这个问题?

这个值加一个属性,记录一下修改的次数(版本号),这个值只增不减,只要这个值做了修改就+1.

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

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

相关文章

基于WebAssembly构建Web端音视频通话引擎

Web技术在发展&#xff0c;音视频通话需求在演进&#xff0c;怎么去实现新的Web技术点在实际应用中的值&#xff0c;以及给我们带来更大的收益是需要我们去探索和实践的。LiveVideoStackCon 2022北京站邀请到田建华为我们从实践中来介绍WebAssembly、WebCodecs、WebTransport等…

Spring 事务的相关配置、传播行为、隔离级别及注解配置声明式事务

目录 一、事务的相关配置 1. 添加测试标签 2. 添加对应方法 3. 测试 二、事务的传播行为 三、事务的隔离级别 四、注解配置声明式事务 1. 注册事务注解驱动 2. 加上注解 3. 配置类代替xml文件中的注解事务支持 4. 测试 往期专栏&文章相关导读 1. Maven系列专栏…

css基础(三)

目录 一、CSS三大特性 1.层叠性 2.继承性 3.行高的继承 4.CSS三大特性之优先级 5.优先级注意的问题 6.CSS权重的叠加 二、盒子模型 1.盒子模型组成部分 2.盒子模型边框border 3.边框的复合写法 4.表格细线边框 5.边框会影响盒子实际大小 6.盒子模型内边距padding 7.盒子模型外边…

java并发编程 4:线程的暂停唤醒 wait/notify与park/unpark

目录 wait & notifywait notify 原理常用APIsleep(long n)和wait(long n)的区别wait notify的使用套路同步模式之保护性暂停实现带超时版多任务版 生产者/消费者模式 park & unpark基本使用原理 wait & notify wait notify 原理 假设一个线程&#xff0c;在获取锁…

【观察】新华三:数据中心可组合架构创新,提供多元算力的“最优解”

今天&#xff0c;以ChatGPT为代表的AIGC大模型&#xff0c;已经在国内形成了“海啸效应”&#xff0c;几乎所有的科技公司都在想方设法进入大模型的赛道。背后的核心驱动力&#xff0c;就在于大模型的最大价值在于普遍提升个人生产力&#xff0c;而各行各业的公司都在积极寻找应…

Ubuntu离线安装Telnet服务

通过ssh上传telnet包&#xff0c;下载地址&#xff1a;telnet-0.17-41.2build1-amd64资源-CSDN文库 解压telnet包&#xff1a; tar -xzvf telnet_0.17-41.2build1_amd64.tar.gz 安装telnet服务&#xff1a; dpkg -i telnet_0.17-41.2build1_amd64.deb 安装完毕&#xff0c;测…

Python通过私信消息提取博主的赠书活动地址

文章目录 前言背景设计开发1.引入模块2.获取私信内容3.根据文本提取url的方法4.获取包含‘书’的url5.程序入口 效果总结最后 前言 博主空空star主页空空star的主页 大家好&#xff0c;我是空空star&#xff0c;本篇给大家分享一下《通过私信消息提取博主的赠书活动地址》。 背…

【Spring】基于注解方式存取JavaBean:Spring有几种注入方式?有什么区别?

前言 Hello&#xff0c;我是小黄。众所周知&#xff0c;Spring是一个开源的Java应用程序框架&#xff0c;其中包括许多通过注解实现依赖注入的功能。Spring提供了多种注入方式&#xff0c;可以满足不同的需求和场景。常见的注入方式包括构造函数注入、Setter方法注入和属性注入…

Qt控件(按钮、单选、复选、list、tree、table)

一、布局 工具栏图标文字一起显示&#xff0c;背景透明。 二、代码 widget.ui <?xml version"1.0" encoding"UTF-8"?> <ui version"4.0"><class>Widget</class><widget class"QWidget" name"Widg…

RabbitMQ应用场景和集群搭建复习

RabbitMQ应用场景和集群搭建 1. MQ的应用场景1.1 异步处理1.2 应用解耦1.3 流量削峰 2、RabbitMQ集群搭建2.1 普通集群(副本集群)2.1.1 架构图2.1.2 集群搭建1、集群规划&#xff1a;这里用三台虚拟机测试2、克隆三台机器主机名和ip映射3、 在其他两台节点上安装rabbitmq4、后台…

模型实战(13)之YOLOv8实现手语字母检测与识别+权重分享

YOLOv8实现手语字母检测与识别+权重分享 本文借助yolov8 实现手语字母的检测与识别:先检测手的ROI,进而对手语表达的字母含义进行识别全文将从环境搭建、模型训练及预测来展开对整个算法流程进行讲解文中给出了开源数据集链接及从 Roboflow 上的下载教程实现效果如下: 1. 环…

目标检测基础

MTCNN 人脸检测 MTCNN&#xff0c;Multi-task convolutional neural network&#xff08;多任务卷积神经网络&#xff09;&#xff0c;将人脸区域检测与人脸关键点检测放在了一起&#xff0c;它的主题框架类似于cascade。总体可分为P-Net、R-Net、和O-Net三层网络结构。这三个…