多线程(进阶二: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成功。


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

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

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

相关文章

C语言——字符函数和字符串函数(一)

&#x1f4dd;前言&#xff1a; 这篇文章对我最近学习的有关字符串的函数做一个总结和整理&#xff0c;主要讲解字符函数和字符串函数&#xff08;strlen&#xff0c;strcpy和strncpy&#xff0c;strcat和strncat&#xff09;的使用方法&#xff0c;使用场景和一些注意事项&…

Java入门项目--蚂蚁爱购

简介 这是一个靠谱的Java入门项目实战&#xff0c;名字叫蚂蚁爱购。 从零开发项目&#xff0c;视频加文档&#xff0c;十天就能学会开发JavaWeb项目&#xff0c;教程路线是&#xff1a;搭建环境> 安装软件> 创建项目> 添加依赖和配置> 通过表生成代码> 编写Ja…

解锁MySQL的威力:针对常见问题的快速解决指南

数据库和表的创建 创建数据库&#xff1a; CREATE DATABASE IF NOT EXISTS MyDatabase; USE MyDatabase;案例&#xff1a; 想象您要开始一个博客项目。首先&#xff0c;您需要一个地方来存储所有的文章和用户信息。上述命令帮助您创建了这样一个存储空间&#xff0c;名为MyDa…

RocketMQ-源码架构二

梳理一些比较完整&#xff0c;比较复杂的业务线 消息持久化设计 RocketMQ的持久化文件结构 消息持久化也就是将内存中的消息写入到本地磁盘的过程。而磁盘IO操作通常是一个很耗性能&#xff0c;很慢的操作&#xff0c;所以&#xff0c;对消息持久化机制的设计&#xff0c;是…

蒙特霍尔问题(选择三扇门后的车与羊)及其贝叶斯定理数学解释

1. 蒙特霍尔问题 有一个美国电视游戏节目叫做“Let’s Make a Deal”&#xff0c;游戏中参赛者将面对3扇关闭的门&#xff0c;其中一扇门背后有一辆汽车&#xff0c;另外两扇门后是山羊&#xff0c;参赛者如果能猜中哪一扇门后是汽车&#xff0c;就可以得到它。 通常&#xf…

javaee实验:文件上传及拦截器的使用

目录 文件上传ModelAttribute注解实验目的实验内容实验过程项目结构编写代码结果展示 文件上传 Spring MVC 提供 MultipartFile 接口作为参数来处理文件上传。 MultipartFile 提供以下方法来获取上传的文件信息&#xff1a;  getOriginalFilename 获取上传的文件名字&#x…

飞天使-linux操作的一些技巧与知识点2

TCP 的三次握手 第一次&#xff0c;客户端与服务端建立链接&#xff0c;需要发送请求连接的消息 第二次&#xff0c;服务端接口到数据后&#xff0c;返回一个确认的操作*&#xff08;至此客户端和服务端链路建立成功&#xff09; 第三次&#xff0c;服务端还需要发送要与客户端…

【Linux】探索Linux进程状态 | 僵尸进程 | 孤儿进程

最近&#xff0c;我发现了一个超级强大的人工智能学习网站。它以通俗易懂的方式呈现复杂的概念&#xff0c;而且内容风趣幽默。我觉得它对大家可能会有所帮助&#xff0c;所以我在此分享。点击这里跳转到网站。 目录 一、进程状态1.1运行状态1.2阻塞状态1.3挂起状态 二、具体L…

React中使用react-json-view展示JSON数据

文章目录 一、前言1.1、在线demo1.2、Github仓库 二、实践2.1、安装react-json-view2.2、组件封装2.3、效果2.4、参数详解2.4.1、src(必须) &#xff1a;JSON Object2.4.2、name&#xff1a;string或false2.4.3、theme&#xff1a;string2.4.4、style&#xff1a;object2.4.5、…

[ROS2] --- service

1 service介绍 1.1 service概念 话题通信是基于订阅/发布机制的&#xff0c;无论有没有订阅者&#xff0c;发布者都会周期发布数据&#xff0c;这种模式适合持续数据的收发&#xff0c;比如传感器数据。机器人系统中还有另外一些配置性质的数据&#xff0c;并不需要周期处理&…

C#,图算法——以邻接节点表示的图最短路径的迪杰斯特拉(Dijkstra)算法C#程序

1 文本格式 using System; using System.Text; using System.Linq; using System.Collections; using System.Collections.Generic; namespace Legalsoft.Truffer.Algorithm { public class Node // : IComparable<Node> { private int vertex, weigh…

【分布式微服务专题】从单体到分布式(一、SpringCloud项目初步升级)

目录 前言阅读对象阅读导航前置知识笔记正文一、单体服务介绍二、服务拆分三、分布式微服务升级前的思考3.1 关于SpringBoot/SpringCloud的思考【有点门槛】 四、SpringCloud升级整合4.1 新建父子项目 学习总结感谢 前言 从本节课开始&#xff0c;我将自己手写一个基于SpringC…

如何轻松恢复 Windows 中删除的文件夹

我们都曾经历过这样的事&#xff0c;而且我们中的大多数人可能很快就会再次这样做。我们讨论的是在 Windows 中按“Delete”或“ShiftDelete”键意外删除重要文件夹的情况。 如果您刚刚按下删除键且未超过 30 天&#xff0c;或者尚未清空回收站&#xff0c;则可以恢复文件夹。…

操作系统学习笔记---内存管理

目录 概念 功能 内存空间的分配和回收 地址转换 逻辑地址&#xff08;相对地址&#xff09; 物理地址&#xff08;绝对地址&#xff09; 内存空间的扩充 内存共享 存储保护 方式 源程序变为可执行程序步骤 链接方式 装入方式 覆盖 交换 连续分配管理方式 单一连…

python安装与工具PyCharm

摘要&#xff1a; 周末闲来无事学习一下python&#xff01;不是你菜鸡&#xff0c;只不过是对手太强了&#xff01;所以你要不断努力&#xff0c;去追求更高的未来&#xff01;下面先了解python与环境的安装与工具的配置&#xff01; python安装&#xff1a; 官网 进入官网下载…

git 关于分支、merge、commit提交

最近开始用git终端提交代码&#xff0c;梳理了一些知识点 一 关于分支 关于分支&#xff0c;git的分支分为本地分支远程分支两种分支&#xff0c;在上传代码时&#xff0c;我们要确保当前本地分支连接了一个远程分支。 我们可以通过下面代码查看当前的本地分支&#xff1a; g…

迅为3588开发板 sudo: 无法解析主机:/DNS配置

环境申明 RK3588 ubuntu 22.04 jammy 迅为开发板 hostname 看是否有Host .&#xff0c;如果没有&#xff0c; sudo vim /etc/hostname在里面加一行&#xff0c;我这就这一个 iTOP-RK3588hosts 修改本地hosts sudo vim /etc/hosts127.0.0.1 localhost localhost iTOP-RK3…

2.postman环境变量及接口关联

一、环境变量以及全局变量 操作流程 1.点击environment 2.点击environment右侧号&#xff0c;新增环境变量 3.在变量中输入变量名以及变量值 4.回到collection页面&#xff0c;修改变量环境 5.在collection中通过{{变量名}}调用变量 变量定义 环境变量&#xff1a;环境变量…

vue 限制在指定容器内可拖拽的div

<template><div class"container" id"container"><div class"drag-box center" v-drag v-if"isShowDrag"><div>无法拖拽出容器的div浮窗</div></div></div> </template><script&g…

P11 Linux进程编程exec族函数

前言 &#x1f3ac; 个人主页&#xff1a;ChenPi &#x1f43b;推荐专栏1: 《Linux C应用编程&#xff08;概念类&#xff09;_ChenPi的博客-CSDN博客》✨✨✨ &#x1f525; 推荐专栏2: 《C_ChenPi的博客-CSDN博客》✨✨✨ &#x1f6f8;推荐专栏3: ​​​​​​《链表_C…