《探索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值,只要将各个槽中的变量值累加返回。




最后


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

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

相关文章
|
Java 索引
多线程和并发编程(2)—CAS和Atomic实现的非阻塞同步
多线程和并发编程(2)—CAS和Atomic实现的非阻塞同步
80 1
多线程和并发编程(2)—CAS和Atomic实现的非阻塞同步
|
12月前
|
Cloud Native Go C语言
C 语言的 互斥锁、自旋锁、原子操作
C 语言的 互斥锁、自旋锁、原子操作
|
缓存 Java 编译器
【并发编程的艺术】内存语义分析:volatile、锁与CAS
几个理解下面内容的关键点:cpu缓存结构、可见性、上一篇文章中的总线工作机制。通过系列的前面几篇文章,我们可以初步总结造成并发问题的原因,一是cpu本地内存(各级缓存)没有及时刷新到主存,二是指令重排序造成的执行乱序导致意料之外的结果,归根结底是对内存的使用不当导致的问题。
117 0
|
5月前
|
安全 前端开发 Java
并发编程之原子操作Atomic&Unsafe
并发编程之原子操作Atomic&Unsafe
|
3月前
|
安全 Java API
Java多线程编程:使用Atomic类实现原子操作
在Java多线程环境中,共享资源的并发访问可能导致数据不一致。传统的同步机制如`synchronized`关键字或显式锁虽能保障数据一致性,但在高并发场景下可能导致线程阻塞和性能下降。为此,Java提供了`java.util.concurrent.atomic`包下的原子类,利用底层硬件的原子操作确保变量更新的原子性,实现无锁线程安全。
29 0
|
4月前
|
安全 Oracle Java
(四)深入理解Java并发编程之无锁CAS机制、魔法类Unsafe、原子包Atomic
其实在我们上一篇文章阐述Java并发编程中synchronized关键字原理的时候我们曾多次谈到过CAS这个概念,那么它究竟是什么?
|
11月前
|
API 调度 C语言
互斥锁,自旋锁,原子操作的原理,区别和实现
v互斥锁,自旋锁,原子操作的原理,区别和实现
118 0
|
6月前
|
安全 中间件 编译器
【C/C++ 原子操作】深入浅出:从互斥锁到无锁编程的转变 - 理解C++原子操作和内存模型
【C/C++ 原子操作】深入浅出:从互斥锁到无锁编程的转变 - 理解C++原子操作和内存模型
3067 3
多线程的原子操作
多线程的原子操作
63 0
|
6月前
|
安全 Linux 调度
Linux C/C++ 开发(学习笔记四):多线程并发锁:互斥锁、自旋锁、原子操作、CAS
Linux C/C++ 开发(学习笔记四):多线程并发锁:互斥锁、自旋锁、原子操作、CAS
82 0