《探索CAS和Atomic原子操作:并发编程的秘密武器》

简介: 《探索CAS和Atomic原子操作:并发编程的秘密武器》

前言


CAS(Compare and Swap)和Atomic原子操作是现代并发编程中的关键工具,它们为多线程环境下的数据共享和同步提供了强大的支持。本文将深入剖析CAS和Atomic操作的原理与应用,探讨它们如何在多线程程序中确保数据的一致性和线程安全性。无论您是初学者还是有经验的开发人员,都将从本文中获得有关并发编程的宝贵见解,使您能够更好地利用这些强大的工具来构建高效、可靠的并发应用程序。




CAS和Atomic原子操作


i++操作不是线程安全的

volatile只能保证可见性可有序性,无法保证i++的原子性

保证原子性的方法

  1. synchronized可以,但是需要切换到内核态,很消耗资源,i++不会去使用
  2. ReentrantLock lock() unlock()放到finally中
  3. CAS CAScompare and swap 比较并交换,修改旧的值,返回新的值



CAS操作修改变量V的值


  • V:内存中的实际值,有可能被其他线程修改
  • E:旧值,当前线程之前从内存中获取的值,也就是参与和V进行比较的值
  • U:当前线程需要更新的值,也就是需要参与和V进行交换的值
  1. 读取变量V的值=5,赋值给E。(E=5) 此时可能会有其他线程进来修改V的值
  2. 计算E+1=6结果赋值给U
  3. 经过时间线,再次判断(E==V),如果相等的话说明其他线程没有修改,把U赋值给V,返回E



在操作系统层面保证这一段代码的原子性

if (V == E) {
    V = U
}


Java实现CAS在Unsafe中,提供了

// 四个参数分别是:对象实例,字段的内存偏移量,字段的期望值,字段更新值
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);


单纯的CAS不能保证可见性,加上Lock前缀指令,就保证了原子性

偏移量

可以理解为变量在内存中的位置


例子:

class Entity(){
  int x;
}


对象头占8个字节,指针占4个字节(64位开启压缩指针),保证8的整数倍就会还有数据填充位。整个对象是16个字节,此时x的偏移量就是12。

如果改成

class Entity(){
    int y;
  int x;
}


这个时候x的偏移量就是从16开始




CAS在JVM中


  1. 根据偏移量计算value地址
  2. cas成功,返回期望值e, 等于e,方法返回true
  3. cas失败,返回内存中的value值,不等于e,方法返回false
#unsafe.cpp
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
  UnsafeWrapper("Unsafe_CompareAndSwapInt");
  oop p = JNIHandles::resolve(obj);
  // 根据偏移量,计算value的地址
  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
  // Atomic::cmpxchg(x, addr, e) cas逻辑 x:要交换的值   e:要比较的值
  //cas成功,返回期望值e,等于e,此方法返回true 
  //cas失败,返回内存中的value值,不等于e,此方法返回false
  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END

不管是 Hotspot 中的 Atomic::cmpxchg 方法,还是 Java 中的 compareAndSwapInt 方法,它们本质上都是对相应平台的 CAS 指令的一层简单封装。CAS 指令作为一种硬件原语,有着天然的原子性,这也正是 CAS 的价值所在。




CAS的缺陷:

  1. 一直自旋获取锁不成功,会导致cpu空转,给cpu打开很大的开销
  2. 只能保证一个共享变量的原子操作
  3. ABA




Atomic包,cas保证原子操作


ABA

问题描述:当有多个线程对一个原子类进行操作的时候,某个线程在短时间内将原子类的值A修改为B,又马上将其修改为A,此时其他线程不感知,还是会修改成功。

解决方法:使用版本号, AtomicStampedReference<V>

reference是实际存储的变量,stamp是版本,每次修改可以通过+1保证版本唯一性

public class AtomicStampedReference<V> {
    private static class Pair<T> {
        // 存储的变量
        final T reference;
        // 版本号,每次修改+1
        final int stamp;
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
        static <T> Pair<T> of(T reference, int stamp) {
            return new Pair<T>(reference, stamp);
        }
    }
    private volatile Pair<V> pair;
}


LongAdder/DoubleAdder

低并发、一般的业务场景下AtomicLong是足够了。如果并发量很多,存在大量写多读少的情况,那LongAdder可能更合适


LongAdder原理

AtomicLong中有个内部变量value保存着实际的long值,所有的操作都是针对该变量进行。也就是说,高并发环境下,value变量其实是一个热点,也就是N个线程竞争一个热点。LongAdder的基本思路就是分散热点,将value值分散到一个数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的long值,只要将各个槽中的变量值累加返回。




最后


本期结束咱们下次再见👋~

🌊 关注我不迷路,如果本篇文章对你有所帮助,或者你有什么疑问,欢迎在评论区留言,我一般看到都会回复的。大家点赞支持一下哟~ 💗

相关文章
|
算法 程序员 C语言
C/C++原子操作与atomic CAS底层实现原理
假定有两个操作A 和B,如果从执行A 的线程来看,当另一个线程执行B 时,要么将B 全部执行完,要么完全不执行B,那么A 和B 对彼此来说是原子的。
996 1
C/C++原子操作与atomic CAS底层实现原理
|
前端开发 Java 开发者
React 之如何调试源码
React 源码如何调试,想必大家在阅读源码的时候一定会遇到,所以本篇我们来讲讲如何进行源码调试。
517 0
|
SQL 关系型数据库 数据库
【微服务系列笔记】Seata
Seata是一种开源的分布式事务解决方案,旨在解决分布式事务管理的挑战。它提供了高性能和高可靠性的分布式事务服务,支持XA、TCC、AT等多种事务模式,并提供了全局唯一的事务ID,以确保事务的一致性和隔离性。Seata还提供了分布式事务的协调、事务日志、事务恢复等功能,帮助开发人员简化分布式事务的管理和实现。
381 1
|
SQL 监控 AliSQL
RDS MySQL最新发布的经济规格,包月仅需18元
RDS基础版经济规格,高性价比,让开发更轻松,学习更高效
1784 15
|
机器学习/深度学习 人工智能 达摩院
ModelScope 社区介绍和操作入门| 学习笔记
快速学习 ModelScope 社区介绍和操作入门
ModelScope 社区介绍和操作入门| 学习笔记
|
机器学习/深度学习 数据采集 人工智能
机器学习实战 | SKLearn入门与简单应用案例
本篇内容介绍了SKLearn的核心板块,并通过SKLearn自带的数据集,讲解一个典型应用案例。
1416 0
机器学习实战 | SKLearn入门与简单应用案例
|
11月前
|
开发框架 人工智能 小程序
小程序常见的 UI 框架
【10月更文挑战第17天】小程序 UI 框架为开发者提供了便捷的工具和资源,帮助他们快速构建高质量的小程序界面。在选择框架时,需要综合考虑各种因素,以找到最适合项目的解决方案。随着技术的不断进步,UI 框架也将不断发展和创新,为小程序开发带来更多的便利和可能性。
722 58
|
10月前
|
芯片 开发者
脉冲宽度调制
脉冲宽度调制(PWM)是一种通过调整脉冲信号的占空比来控制功率、亮度或速度等参数的技术,广泛应用于电机控制、电源转换和照明等领域。
|
10月前
|
存储 缓存 前端开发
聊聊公众号联动扫码登录功能如何实现
【10月更文挑战第30天】公众号联动扫码登录功能的实现涉及前端、后端与微信平台的交互。前端设计二维码展示区和轮询机制,后端负责生成二维码、处理微信推送、用户身份验证及登录。整个过程需确保数据交互安全顺畅,提升用户体验。
343 1
|
存储 运维 Kubernetes
JIRA on K8s helm部署实战
JIRA on K8s helm部署实战
546 0