多线程(进阶二:CAS)

目录

一、CAS的简单介绍

CAS逻辑(用伪代码来描述)

二、CAS在多线程中简单的使用

三、原子类自增的代码分析

都看到这了,点个赞再走吧,谢谢谢谢谢


一、CAS的简单介绍

CAS的全称:“Compare And Swap”,字面意思是 “比较和交换”;一个CAS涉及到以下操作,有两个寄存器:A,SwapB,还有内存的值:AddressC。

先判断寄存器A是否和AddressC的值相同,相同,SwapB的值和AddressC的值进行交换,返回成功操作,否则返回失败操作。这里的交换值我们也可以理解成赋值操作,因为寄存器中的值我们不关心,用完就丢掉了,只关心内存中的值。

CAS逻辑(用伪代码来描述)

伪代码:

这里有两个寄存器:expectValue,swapValue,还有内存的值:address,初始化如图:

进入if语句,判断内存的值和寄存器e的值是否相等,如果相等,就交换寄存器swap和内存address的值,如图:

如图:

然后返回true,如果if条件不成立则返回false。

在计算机中,上述操作在计算机只是一条指令,因为单个指令的原因,所以CAS指令是原子的

CAS指令不涉及到加锁,阻塞。基于CAS指令,合理使用的话,在多线程中,我们可以实现无锁编程;因为之前我们讨论并发编程的线程安全问题时,是通过加锁,阻塞这样方式解决线程安全问题,因为会有阻塞,所以性能也就会降低,用CAS指令实现无锁编程,也能保证线程安全,不涉及到阻塞,这样性能就能得到很大的提升,在多线程编程中打开了新世界的大门。


二、CAS在多线程中简单的使用

因为CAS是CPU的指令,有的cpu可能不支持CAS,但主流的CPU(x86,arm...)都是支持的。

CAS本身是CPU的指令,操作系统对其做了封装,jvm又对操作系统提供的api又做了一层封装。java中CAS的api是放在unsafe中的,这个包名的意思,顾名思义也是“不安全”的意思,一般在java中不建议使用,java的标准库中,对于CAS进行了进一步的封装,把CAS的一些操作封装成工具类,供程序猿使用。而主要的一个工具,叫做 “原子类”。

java.util.concurrent.atomic  包下,里面的类就是基于上述方式实现的,典型的类就是AtomicInteger类,里面有很多方法可以实现数值的自增、自减,以及基本的加减操作。下面的代码案例也是使用AtomicInteger展示。

我们以前写过一个代码,让两个线程实现一个变量自增10_0000次,如下代码是线程不安全。

public class AtomicIntegerTest1 {
    public static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                count++;
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                count++;
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);
    }
}

执行结果:

输出结果也和我们预期结果不同,肯定是线程不安全的,而原因就是因为count++操作不是原子的,在计算机中有三个指令

这里,我们使用CAS的方式,来实现让两个线程实现一个变量自增10_0000次,代码如下

public class AtomicIntegerTest1 {
    public static AtomicInteger count = new AtomicInteger(0);
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                count.getAndIncrement();//和count++意思一样
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                count.getAndIncrement();//和count++意思一样
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);
    }
}

执行结果:

因为这里用CAS的方式,原本count++操作在计算机中有3条指令,不是原子的,肯定线程不安全;但是用CAS的方式就可以把像count++这样的操作,用一条指令完成,是原子的,在这里就不涉及到线程安全问题了。

AtomicInteger中有很多方法:自增,自减,+=,-=等待,这里就不展开讨论了。


三、原子类自增的代码分析

代码还是上述的代码,如下:

public class AtomicIntegerTest1 {
    public static AtomicInteger count = new AtomicInteger(0);
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                count.getAndIncrement();//和count++意思一样
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                count.getAndIncrement();//和count++意思一样
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);
    }
}

在标准库中,getAndIncrement方法是这样的:

这里比较难理解,我们用伪代码的形式介绍,伪代码如下:

当有两个线程对同一个变量进行自增操作时,执行流程是这样的:

这里的oldValue是寄存器中的值,它的值就是AtomicInteger括号里面初始化的值,value是内存中的值。从上到下,把内存value的值都赋值给oldValue,两个线程拿到的都是同一个内存value的值。

这时,右边的线程while循环里的判断,先执行CAS这里的操作,如图:

        

执行完CAS操作后返回true,然后while循环里true != ture,条件不成立,不执行while循环内的代码。

当左边线程进入while循环里面的判断语句时,如图:

也会进入CAS操作,这里因为内存value的值修改了,当前线程寄存器oldValue值还是0,给oldValue+1,会返回false,这时循环条件false != true成立,就会执行oldValue = value操作,如图:

再次进入循环条件里面执行CAS操作,这时候value == oldValue,所以会让oldvalue+1赋值给value,这时候value的值就是2了,如图:

所以,两个线程不管顺序是啥样的,使用getAndIncrement方法都不会出现线程安全问题,因为CAS操作本身就是原子的,原因的逻辑理解也大概是下面这样的:

如果cas不成功,会重复上面的操作,再次读取数据,这次读取到的数据就是正确的了,cas也就能成功。意思就是这个方法里面会判断拿到的值是不是最新值,如果不是就去拿最新的值,再去CAS,这时候因为拿到的是最新值,所以这时能CAS成功。


都看到这了,点个赞再走吧,谢谢谢谢谢

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

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

相关文章

《PySpark大数据分析实战》图书上线啦

《PySpark大数据分析实战》图书上线啦 《PySpark大数据分析实战》图书上线啦特殊的日子关于创作关于数据关于Spark关于PySpark关于图书/专栏 《PySpark大数据分析实战》图书上线啦 特殊的日子 不知不觉一转眼入驻CSDN已经满一年了&#xff0c;这真是一个充满意义的特殊的日子&…

《python每天一小段》--12 数据可视化《1》

欢迎阅读《Python每天一小段》系列&#xff01;在本篇中&#xff0c;将使用Python Matplotlib实现数据可视化的简单图形。 文章目录 一、概念&#xff08;1&#xff09;安装matplotlib&#xff08;2&#xff09;数据可视化实现步骤 二、绘制简单的折线图&#xff08;1&#xff…

在无网络的VMware CentOS7上传并运行Swing Jar文件的方案

文章目录 前言1、配置VNC环境1.1、下载tigervnc-server1.2、下载xorg-x11-xauth1.4、安装1.4.1、安装tigerVNC1.4.2、安装xorg-x11-xauth 1.5、测试vncserver1.6、关闭防火墙 2、安装UltraVNC3、连接UltraVNC4、执行jar文件4.1、生成jar包4.2、运行jar包4.3、解决无法连接错误 …

Java线程概念详解

线程 概念 1.程序:未解决某种问题,使用计算机语言编写的一些列指令(代码)的集合 2.进程:正在运行的程序(被加载到内存中),是操作系统进行资源分配的最小单位 3.线程:进程可以进一步细化为线程(比进程更小)且线程是隶属于进程的,是操作系统执行的最小的执行单元 也是cpu进行任…

通过Nginx的log日志对站点进行数据统计

文章目录 前言统计独立ip访问数量查看访问最频繁的前100个IP查看访问100次以上的IP查询某个IP的详细访问情况,按访问频率排序统计所有的PV数统计当天的PV数查看访问最频的页面(TOP100)每分钟请求量统计每小时请求量统计可视化报表工具 前言 请自行确认nginx的日志是否开始且知…

HarmonyOS--ArkTS(0)--目录

官方API文档&#xff1a; HarmonyOS应用开发官网 - 华为HarmonyOS打造全场景新服务 华为开发者官方网站_创新从这里开始

deque容器

deque容器 文章目录 deque容器一、头文件二、deque容器基本概念三、deque构造函数四、deque赋值操作五、deque大小操作六、deque插入和删除七、deque数据存取八、deque排序 一、头文件 #include <deque>二、deque容器基本概念 功能: ●双端数组&#xff0c;可以对头端进…

【Python】简单的翻译软件

用translate包和tkinter写一个简单的桌面翻译软件。 1、窗口设置&引入包&#xff1a; from tkinter import * from tkinter.ttk import * from tkinter.messagebox import * import translatewinTk() win.title(翻译) win.geometry("600x400")win.mainloop() …

【PWN】学习笔记(一)【二进制基础】

目录 课程教学一次简单的Hack程序的编译与链接Linux下的可执行文件格式ELF进程虚拟地址空间程序的编译与链接程序的装载与进程的执行x86&amd64汇编简述 课程教学 课程链接&#xff1a;https://www.bilibili.com/video/BV1854y1y7Ro/?vd_source7b06bd7a9dd90c45c5c9c44d12…

C++ queue 和priority_queue

目录 1.什么是queue 2.模拟实现 3.仿函数 模板参数Compare 仿函数 4.什么是priority_queue 模拟实现 1.什么是queue 1.队列是一种容器适配器&#xff0c;专门用于在FIFO上下文(先进先出)中操作&#xff0c;其中从容器一端插入元素&#xff0c;另一端提取元素。 2.队列作为…

APP备案,最新获取安卓签名文件中MD5等信息方法

1.通过签名文件获取SHA1和SHA256 直接通过cmd执行命令 keytool -list -v -keystore xxxxx/xxx/xx/xxx.keystore输入后回车会提示输入密码库口令&#xff0c;直接输入Keystore密码&#xff08;输入过程中终端上不会显示&#xff0c;输完回车就行&#xff09; 2.获取md5 由于…

从线性回归到神经网络

一、线性回归关键思想 1、线性模型 2、基础优化算法 二、线性回归的从零开始实现 在了解线性回归的关键思想之后&#xff0c;我们可以开始通过代码来动手实现线性回归了。在这一节中&#xff0c;我们将从零开始实现整个方法&#xff0c;包括数据流水线、模型、损失函数和小批量…

js判断是否对象自身为空

文章目录 一、前言二、JSON.stringify三、for in 配合 hasOwnProperty四、Object.keys五、Object.getOwnPropertyNames六、Object.getOwnPropertyNames 结合 Object.getOwnPropertySymbols七、Reflect.ownKeys八、最后 一、前言 如何判断一个对象为空&#xff1f; 先上结论&a…

前端面试——CSS面经(持续更新)

1. CSS选择器及其优先级 !important > 行内样式 > id选择器 > 类/伪类/属性选择器 > 标签/伪元素选择器 > 子/后台选择器 > *通配符 2. 重排和重绘是什么&#xff1f;浏览器的渲染机制是什么&#xff1f; 重排(回流)&#xff1a;当增加或删除dom节点&…

深入理解Dubbo-3.高级功能剖析和原理解析

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱吃芝士的土豆倪&#xff0c;24届校招生Java选手&#xff0c;很高兴认识大家&#x1f4d5;系列专栏&#xff1a;Spring源码、JUC源码、Kafka原理、分布式技术原理&#x1f525;如果感觉博主的文章还不错的话&#xff…

深入理解JavaScript的箭头函数

深入理解JavaScript的箭头函数 在ES6中&#xff0c;JavaScript引入了箭头函数的概念&#xff0c;它提供了一种更简洁的语法来定义匿名函数。虽然箭头函数看起来很简单&#xff0c;但它们在实际应用中有一些独特的特性和行为。让我们深入理解箭头函数并学习如何正确地使用它们。…

ES6之Map对象

ES6提供了 Map数据结构。它类似于对象&#xff0c;也是键值对的集合。但是“键”的范围不限于字符串&#xff0c;各种类型的值&#xff08;包括对象&#xff09;都可以当作键。 创建方法 let m new Map()console.log(m)Map的方法 1.set( ) 添加元素 接收两个参数&#xff0c…

iMazing 2.17.10官方中文版含2023最新激活许可证码

iMazing是一款iOS设备管理软件&#xff0c;界面简洁功能丰富&#xff0c;但其中还有一个界面更简洁&#xff0c;功能更精炼的小工具&#xff0c;适合轻量级的用户日常来使用&#xff0c;更加方便快捷。接下来&#xff0c;小编就来教大家如何使用iMazing MiNi&#xff0c;以及它…

2-2、基本数据类型

语雀原文链接 文章目录 1、数据类型分类2、基本数据类型2-1、布尔型boolean2-2、字符型char2-3、整型 byte short int long2-4、浮点型float double 3、基本类型转换byte特例char特例 1、数据类型分类 Java 语言是一种强类型语言。通俗点说就是&#xff0c;在 Java 中存储的数…

卡码网 46携带研究材料 LeetCode 416分割等和数组 1049最后一块石头的重量-ii | 代码随想录25期训练营day42、43

动态规划算法4 卡码网 46 携带研究材料 2023.12.6 题目链接常规二维dp数组方法代码随想录讲解[链接]一维滚动数组方法代码随想录讲解[链接] //二维dp数组做法 #include<bits/stdc.h> using namespace std;int main() {//m为材料种类数&#xff0c;n为行李箱最大空间数…