多线程进阶:常见的锁策略、CAS

news2024/12/26 11:12:10

之前我们所了解的属于多线程的初阶内容。今天开始,我们进入多线程进阶的学习。

锁的策略

乐观锁 悲观锁

这不是两把具体的锁,应该叫做“两类锁”

乐观锁:预测锁竞争不是很激烈(这里做的工作可能就会少一些)

悲观锁,预测锁竞争会很激烈(这里的工作可能就会多一些)

这里都不绝对,悲观和乐观,唯一的区分主要就是看预测锁竞争激烈程度的结论~

轻量级锁 重量级锁

轻量级锁加锁解锁开销比较小~效率更高~

重量级锁加锁解锁开销比较大~效率更低~

多数情况下,乐观锁也是一个轻量级锁

多数情况下,悲观锁也是一个重量级锁

自旋锁 挂起等待锁

自旋锁,是一种典型的轻量级锁:当某个线程没有申请到锁的时候,该线程不会被挂起,而是每隔一段时间检测锁是否被释放。如果锁被释放了,那就竞争锁;如果没有释放,过一会儿再来检测。

挂起等待锁,是一种典型的重量级锁:当某个线程没有申请到锁的时候,此时该线程会被挂起,即加入到等待队列等待。当锁被释放的时候,就会被唤醒,重新竞争锁。

也就是说自旋锁在申请锁的过程中会一直重复申请,会占用大量的cpu资源。挂起等待锁也就可以把cpu省下来干别的事情~

互斥锁 读写锁

互斥锁:像synchronized这样的锁,提供加锁和解锁两个操作~

如果一个线程加锁了,另一个线程也尝试加锁就会阻塞等待~

读写锁:只有一组操作中有读也有写,才会产生竞争。

公平锁 非公平锁

此处应该把公平定义成“先来后到”。

操作系统和Java synchronized 原生都是“非公平锁”。操作系统这里的针对加锁的控制,本身就非常依赖线程调度顺序,这个调度顺序是随机的,不会考虑到这个线程等待多久了~

要想实现公平锁,就得在这个基础上,引入额外的东西(比如一个给锁排序的队列)

可重入锁 不可重入锁

不可重入锁:针对同一个线程连续加锁多次,会出现死锁

可重入锁:针对同一个线程连续加锁多滴,不会出现死锁

synchronized

1.synchronized既是一个悲观锁,又是一个乐观锁。

        synchronized默认是一个乐观锁,但是如果发现当前锁竞争比较激烈,就会变成悲观锁。

2.synchronized既是一个轻量级锁,也是一个重量级锁。

        synchronized默认是一个轻量级锁,如果发现当前锁竞争比较激烈,就会转换成重量级锁。

3.synchronized这里的轻量级锁,是基于自旋锁的方式来实现的。

        synchronized这里的重量级锁,是基于挂起等待锁的方式来实现的。

4.synchronized不是读写锁

5.synchronized是非公平锁

6.synchronized是可重入锁

总结:上述谈到的6种锁策略,可以视为是“锁的形容词”。

CAS

CAS全称Compare and swap,字面意思“比较和交换”

一个 CAS 涉及到以下操作:

我们假设内存中的原数据V,旧的预期值A,需要修改的新值B。

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

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

3. 返回操作是否成功。

最重要的是:CAS的操作是原子的!

CAS的过程并非是通过一段代码实现的,而是通过一条CPU指令完成的。

既然是原子的,那么就可以一定程度上处理线程安全问题~

boolean CAS(address, expectValue, swapValue) {
 if (&address == expectedValue) {
   &address = swapValue;
        return true;
   }
    return false;
}

通过一段伪代码(不能编译运行,只是表达了一个大概的逻辑)可以更好地理解CAS。

CAS的应用场景

1.实现原子类:Java标准库里提供的类

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

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

伪代码:

class AtomicInteger{
    private int value;
    public int getAndIncrement(){
        int oldvalue = value;
        while(CAS(value,oldvalue,oldvalue+1) != true){
            oldvalue = value;
        }
        return oldvalue;
    }
}此处为伪代码

可以把oldvalue理解成为寄存器里的值。

我们就拿伪代码来说明:

正常情况下,oldValue应该和value是一样的值,紧接着这里会产生CAS,把oldValue + 1写到value中去。

但是:可能会执行完把value的值写到oldvalue(寄存器)这一步后,线程发生切换了,另一个线程也进行修改了value的值

此时这个线程回来后,通过CAS判定,就认为value和oldvalue不相等了。

于是就返回false,不进行任何交换。进入循环,循环内部重新读取value的值到oldvalue中去。

此时在比较,发现相等了,进行CAS操作,并返回true,就不进入循环结束了。

原子类这里的实现,每次修改之前,再确认一下这个值是否符合要求。

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

2.实现自旋锁

自旋锁伪代码:

public class SpinLock {
    private Thread owner = null;
    public void lock(){
        // 通过 CAS 看当前锁是否被某个线程持有. 
        // 如果这个锁已经被别的线程持有, 那么就自旋等待. 
        // 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程. 
        while(!CAS(this.owner, null, Thread.currentThread())){
       }
   }
    public void unlock (){
        this.owner = null;
   }
}

while循环中:监测当前的owner是否是null,如果是null,就进行交换,也就是把当前线程的引用赋值给owner 。如果赋值成功,此时循环结束,加锁完成了。
如果当前锁,已经被别的线程占用了,CAS就会发现,,this.owner 不是null,CAS就不会产生赋值,也同时返回false.循环继续执行,并进行下次判定....
 

ABA问题

CAS在运行中的核心:检查value和oldValue是否一致。如果一致,就视为value 中途没有被修改过,所以进行下一步交换操作是没问题的。

但是这里的一致,可能是没改过,也可能是改过,但是改回来了。

把value的值设为A的话,CAS判定value为A,此时可能确实value始终是A,也可能是value本来是A,被改成了B,又被还原成了A。

ABA这个情况,大部分情况下其实是不会对代码产生太大影响的,但是不排除一些极端情况,也是可能造成影响的。

假设我有 100 存款.。我想从 ATM 取 50 块钱,取款机创建了两个线程,并发的来执行 -50 操作,我们期望一个线程执行 -50 成功, 另一个线程 -50 失败。 如果使用 CAS 的方式来完成这个扣款过程就可能出现问题。

正常的过程

1) 存款 100。线程1 获取到当前存款值为 100,期望更新为 50; 线程2 获取到当前存款值为 100,,期望更新为 50。

2) 线程1 执行扣款成功,存款被改成 50。线程2阻塞等待中。

3) 轮到线程2执行了,发现当前存款为 50,和之前读到的 100 不相同,执行失败。

异常的过程

1) 存款 100。线程1 获取到当前存款值为 100,期望更新为 50;线程2 获取到当前存款值为 100,,期望更新为 50。

2) 线程1执行扣款成功,存款被改成 50。线程2阻塞等待中。

3) 在线程2执行之前,我的朋友正好给滑稽转账 50,账户余额变成 100 。

4) 轮到线程2 执行了,发现当前存款为100,和之前读到的100相同,再次执行扣款操作

这个时候扣款就被执行了两次。

解决办法

给要修改的值,引入版本号。在 CAS 比较数据当前值和旧值的同时,也要比较版本号是否符合预期。CAS 操作在读取旧值的同时,也要读取版本号。真正修改的时候,如果当前版本号和读到的版本号相同,则修改数据,并把版本号 + 1 。如果当前版本号高于读到的版本号,操作失败(认为数据已经被修改过了)。

synchronized原理

两个线程,针对一个对象加锁,就会产生阻塞等待。

synchronized内部其实还有一些优化机制,存在的目的就是为了让这个锁更高效,更好用。

1.锁升级、锁膨胀

1)无锁  2)偏向锁  3)轻量级锁  4)重量级锁

synchronized(locker){
}

当代码执行到这个代码块中之后,加锁过程会经历前面说的这几个阶段:

偏向锁:

进行加锁的时候,首先会进入到偏向锁的状态。偏向锁的过程就是:“非必要,不加锁”。

synchronized的时候,并不是真的加锁,先偏向锁状态,做个标记(这个过程是非常轻量的),如果整个使用锁的过程中,都没有出现锁竞争,在synchronized执行完之后,取消偏向锁即可。

(反正也没有锁竞争,这样就开销最低了~)

但是如果使用过程中,另一个线程也尝试加锁,在它加锁之前,迅速地把偏向锁升级成真正的加锁状态,另一个线程也就只能阻塞等待了~

轻量级锁:

当synchronized发生锁竞争的时候,就会从偏向锁,升级成轻量级锁。

此时,synchronized相当于是通过自旋的方式来进行加锁的。刚才那个CAS那里的那个伪代码一样~

synchronized内部的自旋循环中,有个计数器,记录了循环了多少次/多久了,达到一定程度,就结束循环,执行重量级锁的逻辑。

重量级锁:

此时如果线程进行了重量级锁的加锁,并且发生锁竞争,此时线程就会被放到阻塞队列中,暂时不参与CPU调度了~

然后锁被释放了这个线程才有机会被调度到,并且有机会获取到锁。

锁降级:

锁能升级了,但是不能降级。只有锁升级,没有锁降级。除非是另外搞一个对象,重复刚才的从偏向锁开始升级的过程~

2.锁消除

编译器智能的判定,看当前的代码是否是真的要加锁,如果这个场景不需要加锁,程序猿也加了,就自动把锁给干掉~~
StringBuffer关键方法都带有synchronized。
但是如果在单线程中使用StringBuffer, synchronized 加了也白加,此时编译器就会直接把这些加锁操作干掉了。

3.锁粗化

锁的粒度:synchronized包含的代码越多,粒度就越粗,包含的代码就越少,粒度就越细。

通常情况下,认为锁的粒度细一点比较好。加锁的部分,是不能并发执行的。锁的粒度越细,能并发的代码就越多,反之就越少。

但是有些情况下,锁的粒度粗一些反而更好~


 

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

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

相关文章

【操作系统笔记六】内存分配

内存对齐 问题:为什么需要内存对齐呢? 主要原因是为了兼容,为了让程序可以运行在不同的处理器中,有很多处理器在访问内存的时候,只能从特定的内存地址读取数据。换个说法就是处理器每次只能从内存取出特定个数字节的数…

WKB近似

WKB方法用于研究一种特定类型的微分方程的全局性质 很有用这种特定的微分方程形如: 经过一些不是特别复杂的推导,我们可以得到他的WKB近似解。 该近似解的选择取决于函数和参数的性质同时,我们默认函数的定义域为当恒大于零,时: 当…

淘宝/天猫获得淘宝商品详情(关键词搜索,店铺所有商品)API接口返回值说明

淘宝API接口,简单而言,就是一套工具,可以帮助你与淘宝平台的数据与功能进行智能对接。它能够让你的店铺信息、商品信息、用户数据等信息实现高效流通,帮助你更好地理解客户需求 我们深知数据安全的重要性,因此&#x…

【MySQL】MySQL中的复制技术是什么?它有哪些组成部分?

什么是复制(Replication)MySQL复制架构感谢 💖 什么是复制(Replication) 复制技术是MySQL高级特性的基础。它可以将数据从一个 MySQL 实例复制到另一个实例,从而实现数据的同步和备份。 MySQL复制架构 以…

Ansys Zemax | 如何设计光谱仪——理论依据

光谱学是一种无创性技术,是研究组织、等离子体和材料的最强大工具之一。本文介绍了如何利用近轴元件建立透镜—光栅—透镜(LGL)光谱仪模型,使用OpticStudio的多重结构( Multiple Configurations )、评价函数 ( Merit Functions )和ZPL宏等先进功能完成了…

八、伯努利朴素贝叶斯算法(Bernoulli NB,Bernoulli Naive Bayes)(有监督学习)

Bernoulli Naive Bayes:用于多元伯努利模型的Naive Bayes分类器 一、算法思路 与多项式分类器一样,该分类器也适用于离散数据。不同之处在于,多项式分类器适用于出现次数,而伯努利分类器则适用于二进制/布尔特征。 二、官网API…

Unity当中的灯光类型

文章目录 前言一、Directional平行光二、Point点灯三、Spot 聚光灯四、Area面光灯,只用于烘培 前言 Unity当中的灯光类型 一、Directional平行光 Unity当中最重要的灯管类型,类似现实中的太阳光 二、Point点灯 类似现实中的灯泡,萤火虫&a…

Celery结合flask完成异步任务与定时任务

Celery 常用于 web 异步任务、定时任务等。 使用 redis 作为 Celery的「消息代理 / 消息中间件」。 这里通过Flask-Mail使用qq邮箱延时发送邮件作为示例 pip install celery pip install redis pip install Flask-Mail1、使用flask发送邮件 使用 Flask-Mail 发送邮件需要进行…

1688-阿里巴巴批发网(获取优惠券信息)

item_get_app-获取1688app上原数据 为了进行电商平台 的API开发,首先我们需要做下面几件事情。 1)开发者注册一个账号 2)然后为每个1688 应用注册一个应用程序键(App Key) 。 3)下载1688 API的SDK并掌握基本的API基…

惠普P1108激光打印机报错光束探测错误检修

在使用打印机的过程中,有时候会遇到光束探测错误的问题,导致打印机无法正常工作。这个问题可能是由多种原因引起的,包括硬件故障、驱动程序错误、操作系统问题等。在这里,我将为您提供一些解决光束探测错误的方法。 故障描述&…

SpringBoot 学习(七)Swagger

7. Swagger 7.1 简介 便于前后端集成联调RestFul Api 文档在线生成工具 > Api 文档与 Api 定义同步更新直接运行&#xff0c;在线测试 Api 接口 7.2 springboot 集成 swagger (1) 导入依赖 <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger…

清华用7个ChatGPT模拟《狼人杀》,结果出乎意料!

为了验证大语言模型的沟通、规划、反思等拟人化能力&#xff0c;清华研究团队发布了一篇名为“探索大语言模型在交流游戏中的应用&#xff1a;《狼人杀》实验”的研究论文。 结果显示&#xff0c;通过ChatGPT&#xff08;GPT -turbo-0301&#xff09;构建的7个玩家&#xff0c…

【高云FPGA系列教程(11):MultiButton按键驱动模块移植】

文章目录 1. MultiButton简介2. MultiButton代码获取3. MultiButton移植4. 测试与运行本文是高云FPGA系列教程的第11篇文章。 1. MultiButton简介 MultiButton, 一个小巧简单易用的事件驱动型按键驱动模块,可无限量扩展按键,按键事件的回调异步处理方式可以简化你的程序结构…

nssm部署jar包

nssm部署jar包 1、软件下载 官方传送门 csdn下载地址 2、安装服务 F: cd F:\服务启动目录 set JAVA_HOMEF:\Program Files\Java\jdk1.8.0_181 set CLASSPATH.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOMe%\lib\tools.jar; set Path%JAVA_HOME%\bin; java -Dfile.encodingutf-8 -…

解决Vue设置图片的动态src不生效的问题

一、问题描述 在vue项目中&#xff0c;想要动态设置img的src时&#xff0c;此时发现图片会加载失败。在Vue代码中是这样写的&#xff1a; 在Vue的data中是这样写的&#xff1a; 我的图片在根目录下的static里面&#xff1a; 但是在页面上这个图片却无法加载出来。 二、解决方案…

使用原生html<table>构造复杂table表

<table border data-sort"sortDisabled" align"center" class"table"><tbody><tr class"textarea1"><td rowspan"1" colspan"2" class"background-gray"><label>日期<…

更新andriod studio版本,项目编译报could not find org.junit.jupiter:junit-jupiter

原本使用Android Studio 版本是4.1.1&#xff0c;现更新为 点击build -》 build bundle -》build apk&#xff0c;项目报 Could not determine the dependencies of task :app:compileDebugUnitTestJavaWithJavac. > Could not resolve all task dependencies for configur…

前脚收费,后脚道歉?Unity收费新规引众怒,Epic承诺虚幻引擎永久免费

&#xfeff;当你还在纠结开发游戏用Unity引擎还是UE虚幻引擎的时候&#xff0c;Unity已经给游戏开发领域重重一拳。 &#xfeff;Unity作为一款全球范围内广泛使用的游戏引擎&#xff0c;因其价格亲民且灵活而备受众多开发者的青睐。然而在9月12日&#xff0c;Unity突然宣布计…

分析大疆官网根据ip自动跳转到指定域名

如果cookie存在region且有效&#xff0c;跳转到指定域名&#xff0c; 如果cookie存在region且无效&#xff0c;跳转到默认域名&#xff0c; 如果禁用cookie或者cookie中没有region&#xff0c;根据ip自动判断所在地区&#xff0c;跳转到指定域名 1、浏览器地址栏输入dji.com&am…

2018-2022年盟浪 ESG数据

2018-2022年盟浪 ESG数据 1、时间&#xff1a;2018-2022年 2、指标&#xff1a;证券代码、证券简称、盟浪ESG评级、省份、城市、所属证监会行业名称[交易日期] 最新收盘日[行业级别] 大类行业、所属证监会行业代码[交易日期] 最新收盘日[行业级别] 大类行业 3、范围&#xf…