CAS为什么还存在线程安全问题(从所谓的ABA问题再学CAS)

概述

之前学习 CAS,从 Java 代码层面知道其原理,借助一条 CPU 原子指令,通过不断地自旋去比较(compare)和(and)赋值(set)。当时对线程安全的认知停留在将多条 Java 语句组合成一个原子操作,那么就能够保证线程安全。那想着 compare 对应 if,set 对应 =,而 compareAndSet() 又是 CPU 原子指令,那总不能为了学个 CAS 还去研究 CPU 指令吧,CAS 学完了。

直到看到有关 CAS 中 ABA 问题的文章,虽然这些文章对 ABA 问题举的例子普遍都有些烂(虽然 wiki 上面也是同样的例子,但烂就是烂),什么 B 线程将 value 值从 5 改到 6 再改回 5,哪个线程吃饱了没事干,要把一个值从 5 改到 6 再改回 5 呢?看这些文章也没弄明白 ABA 问题,或者说,我最初的疑惑是:compareAndSet 不是 CPU 原子命令吗,为什么 CAS 机制还存在线程安全(ABA)问题? 在弄清楚这个问题之后,感觉 ABA 问题就不是什么问题了。

CAS 机制为什么存在线程安全问题

先从问题出发,为什么 CAS 还存在线程安全问题?

Java内存模型

  1. 首先,需要简单了解 Java 内存模型(JMM),如图所示。

    数据保存在主内存中,线程会拷贝一份到自己的工作内存中作为缓存,所以需要使用 violate 关键字来修饰 value 变量保证可见性。可见性具体来说就是,任意一个线程修改其工作内存中的 value 时,会立即写回主内存,同时让其他线程中的缓存数据无效,其他线程如果需要使用 value,需要重新从主内存中读取最新值进行缓存。

  2. 其次,需要深度追问一下自己,compareAndSet 中 compare 比较的是哪两个对象? compareAndSet 中 set 赋值的是哪个对象? 后面通过 AtomicInteger 的源码简单看下,这里直接上结论:

    • compare 比较的两个对象是线程工作内存中的 value 和主内存中的 value;
    • set 赋值的对象是工作内存中的 value,通过 violate 来实时更新主内存(线程不能直接修改主内存的值)。
  3. 最后,为什么会有线程安全问题,问题发生的时间点在哪?

    compareAndSet 是 compre(比较)和 set(赋值)是原子性操作,但实际涉及到 get-compare -> set 这三个操作,其中 get 是指从主内存获取数据到线程的工作内存的过程。产生线程安全问题的核心就是 CAS 只保证 compare -> set 这两个过程是原子的,但是 get -> compare 并不是原子的,这个时间段(从主内存读取 value 值到实际执行 CAS 指令)会被其它线程趁虚而入。因此如果发生下面的场景,对于一般的多线程环境而言,便出现线程安全问题。但,这不是一般,这是 CAS! 在这里插入图片描述

    CAS 机制流程图

ABA 问题

上面的论断先放一放,先可能带有错误地论述一下个人理解的 ABA 问题和线程安全问题之间的关系。

所谓的 ABA 问题,个人理解成是线程安全问题的其中一种抽象描述,例如我们将上面的 B = B1 + B2,那么可以理解成一个线程的操作插入到另一个线程的两次操作之间,并最终影响到那个线程的执行结果。类似的例子还有:

  • 从数据库的事务角度来说,就是不满足事务的隔离性,事务 B 影响了事务 A。
  • 从分布式系统 CP 架构的数据库和缓存的双写一致性角度来说,假设采用 Read/Write Through 模式,将 A 看做是未命中 Redis 缓存的读请求,B 看做是写请求,那么所谓的 ABA 问题就是写请求在缓存中的更新结果被读请求覆盖,造成缓存和数据库的不一致。如果不考虑缓存自动过期,那么这个不一致的时间需要等到下一次写请求才能够更新缓存。

为什么说 ABA 是线程安全问题的其中一种描述,假设上图中,线程 A 的 CAS 操作在线程 B 的 CAS 操作之前,那么操作失败的就是线程 B 的 CAS 操作,同样线程 A 影响到线程 B,那这种模式可以描述为 ABAB,那 ABAB 可以看做是 ABA 的一种子模式嘛(ABA*),综上,个人认为 ABA 问题(狭义) = 线程安全问题(广义)。

CAS 的特性

从上面的流程图中可以看出,CAS 机制应该包括:compareAndSet 原子操作、自旋。自旋本质上就是重试,可以类比消息队列的消息重发,TCP 的报文重发等。

CAS 这种实现并发的机制,并没有像 synchronized、lock 等锁机制来真正避免线程安全问题,而是通过重试机制来逃避线程安全问题。系统出了问题就卸载重启呗,费那么大劲debug干啥,没错,这就是 CAS 的理念,所以说逃避问题也是解决问题的一种有效方式。

至此,就 CAS 本身的理念而言,不存在线程安全问题,只要按照 CAS 的要求来,完美架构怎么说完美。但是,世界不是完美的,至少计算机的世界不是完美的。问题出现在 compare 上,或者说出现在 get 上。 如果 get 从主内存拷贝到线程的工作内存不是浅拷贝,又或者 compare 可以向 equals() 方法一样进行重载,而不是和 == 一样只能比较地址,那 CAS 也就不存在问题了。

假设 Person value = new Person("root", 18);,那么 value 作为 Person 对象的引用,实际上保存的只是一个地址,而修改对象中的字段属性并不会造成地址发生改变,即 value 的值没有变化。因此线程 B 将 value.name = "admin"; 并不会去更新主内存中的 value,更不会让线程 A 中的 value 缓存失效。在大多数情况下,这可能会造成业务错误,因为在这种情况下,线程 A 和线程 B 都成功执行了 compareAndSet 操作,可能会从逃避错误变成隐藏错误。(一般正常情况下,是线程 B 成功,线程 A 重做)

这种可能隐藏错误的情况,和那个线程 B 将值从 5 变到 6 再变回 5 的案例本质上都是在论述一件事:线程 B 对 value 对象属性的修改没有被线程 A 的 CAS 操作发现。但这称之为错误,个人觉得不合适,因为如果 value 是基本数据类型,那这种特性就是最终一致性(高效的体现),也是 Redis 中 AOF 重写的理念,最后再次批斗一下那个 5/6/5 的案例。
在这里插入图片描述

处理方案

虽然上面嘴硬说那不是错误,那是特性,但是大多数情况下,这个"特性"还是要处理的。

解决办法: 添加一个自增的 int 类型数据(version)作为版本号,在判断的时候不仅判断 value 是否相等,还判断 version 是否相等。

疑问:是否可以通过只判断 version 呢?感觉一般情况下也 ok 啊,不会有让 int 越界然后重复的的并发量吧,有待进一步理解。

相似机制: HashMap、ArrayList 等通过 modCount 来禁止迭代时进行修改,虽然是记录修改次数,但将每一次修改就作为一个新版本来看待的话,也是一样的。

源码简析

public class AtomicInteger{private volatile int value;private static final Unsafe unsafe = Unsafe.getUnsafe();private static final long valueOffset;static {try {// 通过Unsafe + 反射获取到value字段的偏移地址,所以valueOffset就是value在主内存中的地址,因此可以通知更新?valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));} catch (Exception ex) { throw new Error(ex); }}// compareAndSwap是原子指令,但CAS代表一套机制,并不是一个指令。而getAndSet及其源代码更能体现整个过程public final int getAndSet(int newValue) {return unsafe.getAndSetInt(this, valueOffset, newValue);}
}
public final class Unsafe {public final int getAndSetInt(Object o, long offset, int newValue) {int v;do {v = getIntVolatile(o, offset);} while (!compareAndSwapInt(o, offset, v, newValue));return v;}
}

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

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

相关文章

数据管理平台Splunk Enterprise本地部署结合内网穿透实现远程访问

文章目录 前言1. 搭建Splunk Enterprise2. windows 安装 cpolar3. 创建Splunk Enterprise公网访问地址4. 远程访问Splunk Enterprise服务5. 固定远程地址 前言 Splunk Enterprise是一个强大的机器数据管理平台,可帮助客户分析和搜索数据,以及可视化数据…

二维码智慧门牌管理系统升级的重要性与功能

文章目录 前言一、系统的双重作用二、系统的挑战与未来发展三、结论与未来展望四、为未来智慧管理铺平道路 前言 随着科技不断进步,智能化管理已贯穿于我们日常生活的各个领域。其中,二维码智慧门牌管理系统升级解决方案因其独特的考核评估系统和实用功…

基于红外传感的野外变压站生物入侵检测系统(论文+源码)

1. 系统设计 本课题为基于红外传感的野外变压站生物入侵检测系统,主要是针对野外变压站生物入侵的问题进行设计,整个系统的框图如图经过上述的功能需求分析和各个关键模块的选型后,最终得到了如图2.1所示的,采用STC89C52单片机为…

移动安全APP--Frida+模拟器,模拟器+burp联动

最近测APP被通报了,问题点测得比较深,涉及到frida和burp抓包,一般在公司可能会有网络的限制,手机没办法抓包,我就直接在模拟器上试了,就在这记录一下安装过程。 目录 一、Frida安装 二、burp与逍遥模拟器…

外卖系统海外版:技术智能引领全球美食新潮流

随着全球数字化浪潮的推动,外卖系统海外版不仅是食客们品味美食的便捷通道,更是技术智能在美食领域的引领者。本文将深入剖析其背后的技术实现,揭开代码带来的美食革新。 多语言支持:构建全球美食沟通桥梁 def multilingual_su…

获取本机公网内网 ip 地址

获取公网地址 winr 输入 cmd 打开终端 获取公网地址命令 curl http://icanhazip.com # 或者 curl http://ifconfig.me在线工具网址 获取内网 ip ipconfig

WebGL开发虚拟旅游应用

WebGL可以用于开发虚拟旅游应用,提供用户在浏览器中探索虚拟景点和环境的交互体验。以下是在WebGL中开发虚拟旅游应用的一般流程,希望对大家有所帮助。北京木奇移动技术有限公司,专业的软件外包开发公司,欢迎交流合作。 1.需求分析…

B040-SpringMVC进阶 JSON 上传下载 拦截器 执行流程

目录 项目准备JSONJSON作用JSON演示ResponseBody注解日期返回json格式 文件上传与下载文件上传准备工作文件项上传文件完成 文件下载文件下载页面下载业务代码 SpringMVC的执行流程 项目准备 大体步骤: 新建dynamic web project, 修改默认输出的class路…

​ SK Ecoplant借助亚马逊云科技,海外服务器为环保事业注入新活力

在当今全球面临着资源紧缺和环境挑战的大背景下,数字技术所依赖的海外服务器正成为加速循环经济转型的关键利器。然而,很多企业在整合数字技术到运营中仍然面临着一系列挑战,依然存在低效流程导致的不必要浪费。针对这一问题,SK E…

确保调查问卷合理性:设计、实施与评估指南

在如今的信息时代,问卷调查成为了一种常见的数据采集方式。问卷广泛用于市场调研、科学研究、员工幸福评估等各个领域。但是,问卷调查的有效性和可靠性在于问卷设计和实施过程。怎么确保调查问卷的合理性?首先建立研究目的、正确选择问卷种类…

EDA实验-----直流电机驱动设计(Quartus II )

目录 一、实验目的 二、实验仪器设备 三、实验的重点和难点 四、实验原理 五、实验步骤 六、实验报告 七、实验过程 1.分频器代码 2.方向选择器 3.直流电动机工作原理 4.电路连接图 5.文件烧录 一、实验目的 了解直流电机控制的工作原理和实现的方法。掌握PWM波控…

Jupyter Notebook修改默认工作目录

1、参考修改Jupyter Notebook的默认工作目录_jupyter文件路径-CSDN博客修改配置文件 2.在上述博客内容的基础上,这里不是删除【%USERPROFILE%】而是把这个地方替换为所要设置的工作目录路径, 3.【起始位置】也可以更改为所要设置的工作目录路径&#x…

24V输入 输出12V 5A长时间 峰值6A 7A 40V耐压 8-10A输出 大功率降压

24V输入 输出12V 5A长时间 峰值6 A 7A 40V耐压 8-10A输出 大功率降压

动态内存分配(malloc和free​、calloc和realloc​)

目录 一、为什么要有动态内存分配​ 二、C/C中程序内存区域划分​ 三、malloc和free​ 2.1、malloc 2.2、free​ 四、calloc和realloc​ 3.1、calloc​ 3.2、realloc​ 3.3realloc在调整内存空间的是存在两种情况: 3.4realloc有malloc的功能 五、常见的动…

rk3568 bootLoader编译

Linux系统uboot、linux kernel、rootfs移植学习笔记(一)_uboot 删除环境变量-CSDN博客 板信息配置文件:device/rockchip/rk356x/BoardConfig-IAC-RK3568-MB-BETA-V1_00.mk uboot编译入口 Linux系统uboot、linux kernel、rootfs移植学习笔记&…

【jsonview去除排序】如何让jsonview不自动排序(已解决)

✈️涉及知识 如何取消JSON默认数值排序,JSON.parse()函数排序关闭,取消JSON.parse排序,Json格式化校验,jsonview排序问题解决方法。 🥇专栏🥇:前端技术,json格式化 💂关…

天猫数据分析(天猫查数据工具):2023年天猫平台假发行业市场销售数据分析报告

如今,由于人们工作和生活的压力较大,居民脱发问题严重,且脱发群体倾向于80后和90后,逐渐向低龄化发展。除脱发外,在颜值经济的背景下,人们越来越注重外貌和形象,假发作为一种改善发型的工具&…

uniapp-uni-icons组件@click.stop失败解决~

你们好,我是金金金。 场景 可以看见我右侧有两个icon,点击的时候 会影响到折叠面板的打开,这让我很是苦恼,然后我使用了click.stop修饰符阻止事件冒泡 排查 排查之前我先贴一下代码 报错截图 可以看到找不到属性stopPropagation&…

Elasticsearch 性能调优基础知识

Elastic Stack 已成为监控任何环境或应用程序的实际解决方案。 从日志、指标和正常运行时间到性能监控甚至安全,Elastic Stack 已成为满足几乎所有监控需求的一体化解决方案。 Elasticsearch 通过提供强大的分析引擎来处理任何类型的数据,成为这方面的基…

全新Facebook养号指南,怎么做才能不被封号?

最近听很多跨境电商小伙伴们说 Facebook 又被封号了,可能是你的 Facebook 账号还不够稳定,或者说还没有足够的粉丝和互动。如果你想让自己的 Facebook 账号更加稳定,就需要养号。俗话说,一封回到解放前,为什么你明明有…