当前位置: 首页 > news >正文

cas无法使用_并发编程中cas的这三大问题你知道吗?

在java中cas真的无处不在,它的全名是compare and swap,即比较和交换。它不只是一种技术更是一种思想,让我们在并发编程中保证数据原子性,除了用锁之外还多了一种选择。

一.cas的思想

先用一张图了解一下cas的思想。

daf4fb3df1cfaa9819b9743550fa0a6d.png

cas需要四个要素:地址、旧值、期望值 和 新值。

地址:就不用多说了,就是一个引用,可以通过这个引用找到相应的元素,图中没有给出地址,是因为太简单了。

旧值:就是元素中swap前的数据

期望值:其实跟旧值是一样的

新值:计算之后得到的新数据

二.jdk是如何实现cas的

jdk给我们封装了Unsafe类,它里面提供了很多native compareAndSwap开头的方法,我们可以直接使用这些方法实现cas的功能。

public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

此外,还提供了getAndAdd开头的方法,其实就是对compareAndSwap开头的方法做了一次封装,

public final int getAndAddInt(Object var1, long var2, int var4) {    int var5;    do {      var5 = this.getIntVolatile(var1, var2);    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));    return var5;}public final long getAndAddLong(Object var1, long var2, long var4) {    long var6;    do {      var6 = this.getLongVolatile(var1, var2);    } while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));    return var6;}public final int getAndSetInt(Object var1, long var2, int var4) {    int var5;    do {      var5 = this.getIntVolatile(var1, var2);    } while(!this.compareAndSwapInt(var1, var2, var5, var4));    return var5;}public final long getAndSetLong(Object var1, long var2, long var4) {    long var6;    do {      var6 = this.getLongVolatile(var1, var2);    } while(!this.compareAndSwapLong(var1, var2, var6, var4));    return var6;}public final Object getAndSetObject(Object var1, long var2, Object var4) {    Object var5;    do {      var5 = this.getObjectVolatile(var1, var2);    } while(!this.compareAndSwapObject(var1, var2, var5, var4));    return var5;}

它里面会循环调用compareAndSwap开头的方法,一直到成功为止。这里就是所谓的自旋,说白了是死循环的方式。

三.jdk中是如何使用cas的

jdk使用cas两个最典型的应用是atomic包 和 aqs(AbstractQueuedSynchronizer)

先看看AtomicInteger类

//递增方法public final int getAndIncrement() {   return unsafe.getAndAddInt(this, valueOffset, 1);}//递减方法public final int getAndDecrement() {   return unsafe.getAndAddInt(this, valueOffset, -1);}

它其实就是用了上面提到的Unsafe类的getAndAddInt()方法。

当然,这里使用Unsafe类的方法可以保证简单操作的原子性,如果要保证多线程的可见性,还需要使用volatile关键字一起配合使用。

所以,AtomicInteger类的value值需要这样定义:

private volatile int value;

再看看AbstractQueuedSynchronizer类,里面包含了compareAndSet开头的很多方法

private final boolean compareAndSetHead(Node update) {    return unsafe.compareAndSwapObject(this, headOffset, null, update);}private final boolean compareAndSetTail(Node expect, Node update) {    return unsafe.compareAndSwapObject(this, tailOffset, expect, update);}private static final boolean compareAndSetWaitStatus(Node node,                                                     int expect,                                                     int update) {   return unsafe.compareAndSwapInt(node, waitStatusOffset,                                  expect, update);}private static final boolean compareAndSetNext(Node node,                                               Node expect,                                               Node update) {    return unsafe.compareAndSwapObject(node, nextOffset, expect, update);}

它也是用到了Unsafe类,不过它调用的是compareAndSwap开头的方法。

四.cas的三大问题

使用cas保证数据原子性相对于加锁来说确实是一个不错的办法,在JDK中也用得比较多,但是也有它的问题

1.ABA问题

比如线程1 和 线程2 同时获取到数据A,线程1先执行,把数据修改成了B,又修改成了A。此时线程2执行的时候比较后和之前的A相等,就直接交换成了新值。这种情况其实是不应该修改的,因为数据已经发生了变化。

当然直接使用Unsafe类自身是无法解决ABA问题的,那怎么办?

我们需要使用AtomicStampedReference类,增加版本号,比较的时候先比较版本号。

2.循环时间长

一般情况下使用cas要配合自旋(死循环)一起,如果高并发的时候,会出现有很多请求多次循环也成功不了的情况,给cpu带来非常大的消耗。

3.只能保证一个变量的原子性

cas可以保证原子性,但是只能保证一个变量的原子性。我们上面提到的atomic包 和 aqs,也都只能保证一个变量的原子性。因为cas的原子性是靠cpu执行指令的时候内部机制保证的,它只能一次保证比较和交换操作的原子性。

那么问题来了,如果要保证多个变量的原子性该怎么办呢?

可以把多个变量合并成一个变量,然后使用JDK 提供的 AtomicReference 类来保证引用对象之间的原子性,就可以把 多个变量放在一个对象里来进行 CAS 操作。

相关文章:

  • python写微信小程序商城_Python(Django 2.x)+Vue+Uniapp微信小程序商城开发视频教程
  • python中如何将两个列表进行合并_Python中如何把两个list合并,并按从小到大顺序排列?...
  • ctf xss利用_从xss挑战之旅来重读xss(一)
  • python考研参考书目_Python自学日记28——如何选择学习资料
  • 无法应用转换程序_电脑硬盘无法安装应用程序怎么办?
  • educoder实训平台python入门之运算符的使用_Python入门教程-运算符
  • docker rabbitmq_docker快速部署rabbitmq多机集群
  • python程序设计学生的自我总结_《Python程序设计》学生答疑【20200227】
  • ssl 接收到一个超出最大准许长度的记录_从零编写一个自己的蜜罐系统
  • pythondocumentation_python官方文档
  • python怎么突然这么火_为什么python突然变得这么火了?
  • gdb 条件断点_蜂鸟E203系列——Linux调试(GDB+Openocd)
  • gif分解工具_搞笑 GIF 制作工具
  • python123第四周_百度杯十月第四周WriteUp
  • 数据窗口动态生成列_2020 BAT大厂数据分析面试经验:“高频面经”之数据分析篇...
  • [Vue CLI 3] 配置解析之 css.extract
  • [笔记] php常见简单功能及函数
  • 《微软的软件测试之道》成书始末、出版宣告、补充致谢名单及相关信息
  • 【css3】浏览器内核及其兼容性
  • 【翻译】babel对TC39装饰器草案的实现
  • CSS3 变换
  • Eureka 2.0 开源流产,真的对你影响很大吗?
  • HashMap剖析之内部结构
  • Java,console输出实时的转向GUI textbox
  • laravel5.5 视图共享数据
  • niucms就是以城市为分割单位,在上面 小区/乡村/同城论坛+58+团购
  • 动态规划入门(以爬楼梯为例)
  • 这几个编码小技巧将令你 PHP 代码更加简洁
  • 阿里云IoT边缘计算助力企业零改造实现远程运维 ...
  • ​sqlite3 --- SQLite 数据库 DB-API 2.0 接口模块​
  • !!【OpenCV学习】计算两幅图像的重叠区域
  • #162 (Div. 2)
  • #Linux(权限管理)
  • #LLM入门|Prompt#1.8_聊天机器人_Chatbot
  • (20050108)又读《平凡的世界》
  • (NO.00004)iOS实现打砖块游戏(九):游戏中小球与反弹棒的碰撞
  • (紀錄)[ASP.NET MVC][jQuery]-2 純手工打造屬於自己的 jQuery GridView (含完整程式碼下載)...
  • (考研湖科大教书匠计算机网络)第一章概述-第五节1:计算机网络体系结构之分层思想和举例
  • (论文阅读26/100)Weakly-supervised learning with convolutional neural networks
  • *(长期更新)软考网络工程师学习笔记——Section 22 无线局域网
  • ..回顾17,展望18
  • .\OBJ\test1.axf: Error: L6230W: Ignoring --entry command. Cannot find argumen 'Reset_Handler'
  • .NET Core 通过 Ef Core 操作 Mysql
  • .NET/C# 使用 SpanT 为字符串处理提升性能
  • .net之微信企业号开发(一) 所使用的环境与工具以及准备工作
  • @Builder用法
  • @media screen 针对不同移动设备
  • [3D基础]理解计算机3D图形学中的坐标系变换
  • [AutoSAR系列] 1.3 AutoSar 架构
  • [BZOJ2850]巧克力王国
  • [C#]无法获取源 https://api.nuge t.org/v3-index存储签名信息解决方法
  • [c++] 什么是平凡类型,标准布局类型,POD类型,聚合体
  • [Codeforces] probabilities (R1600) Part.1
  • [CodeForces-759D]Bacterial Melee
  • [HNOI2015]实验比较