【JUC】并发编程重点知识——CAS与Atomic原子类总结

目录

一、CAS原理

(一)简介和特点

(二)ABA

(三)CAS 与 synchronized

(四)CAS操作示例

二、Atomic

(一)常用API

(二)原理分析

(三)原子引用

(四)原子数组

(五)原子更新器

(六)原子累加器



一、CAS原理

(一)简介和特点

无锁编程:Lock Free

CAS 的全称是 Compare-And-Swap,是 CPU 并发原语

  • CAS 并发原语体现在 Java 语言中就是 sun.misc.Unsafe 类的各个方法,调用 UnSafe 类中的 CAS 方法,JVM 会实现出 CAS 汇编指令,这是一种完全依赖于硬件的功能,实现了原子操作

  • CAS 是一种系统原语,原语属于操作系统范畴,是由若干条指令组成 ,用于完成某个功能的一个过程,并且原语的执行必须是连续的,执行过程中不允许被中断,所以 CAS 是一条 CPU 的原子指令,不会造成数据不一致的问题,是线程安全的

底层原理:CAS 的底层是 lock cmpxchg 指令(X86 架构),在单核和多核 CPU 下都能够保证比较交换的原子性

  • 程序是在单核处理器上运行,会省略 lock 前缀,单处理器自身会维护处理器内的顺序一致性,不需要 lock 前缀的内存屏障效果

  • 程序是在多核处理器上运行,会为 cmpxchg 指令加上 lock 前缀。当某个核执行到带 lock 的指令时,CPU 会执行总线锁定或缓存锁定,将修改的变量写入到主存,这个过程不会被线程的调度机制所打断,保证了多个线程对内存操作的原子性

作用:比较当前工作内存中的值和主物理内存中的值,如果相同则执行规定操作,否则继续比较直到主内存和工作内存的值一致为止

CAS 特点:

  • CAS 体现的是无锁并发、无阻塞并发,线程不会陷入阻塞,线程不需要频繁切换状态(上下文切换,系统调用)

  • CAS 是基于乐观锁的思想

CAS 缺点:

  • 执行的是循环操作,如果比较不成功一直在循环,最差的情况某个线程一直取到的值和预期值都不一样,就会无限循环导致饥饿,使用 CAS 线程数不要超过 CPU 的核心数,采用分段 CAS 和自动迁移机制

  • 只能保证一个共享变量的原子操作

    • 对于一个共享变量执行操作时,可以通过循环 CAS 的方式来保证原子操作

    • 对于多个共享变量操作时,循环 CAS 就无法保证操作的原子性,这个时候只能用锁来保证原子性

  • 导致 ABA 问题

(二)ABA

ABA 问题:当进行获取主内存值时,该内存值在写入主内存时已经被修改了 N 次,但是最终又改成原来的值

其他线程先把 A 改成 B 又改回 A,主线程仅能判断出共享变量的值与最初值 A 是否相同,不能感知到这种从 A 改为 B 又 改回 A 的情况,这时 CAS 虽然成功,但是过程存在问题

  • 构造方法:

    • public AtomicStampedReference(V initialRef, int initialStamp):初始值和初始版本号

  • 常用API:

    • public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp)期望引用和期望版本号都一致才进行 CAS 修改数据

    • public void set(V newReference, int newStamp):设置值和版本号

    • public V getReference():返回引用的值

    • public int getStamp():返回当前版本号

public static void main(String[] args) {
    AtomicStampedReference<Integer> atomicReference = new AtomicStampedReference<>(100,1);
    int startStamp = atomicReference.getStamp();
    new Thread(() ->{
        int stamp = atomicReference.getStamp();
        atomicReference.compareAndSet(100, 101, stamp, stamp + 1);
        stamp = atomicReference.getStamp();
        atomicReference.compareAndSet(101, 100, stamp, stamp + 1);
    },"t1").start();

    new Thread(() ->{
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (!atomicReference.compareAndSet(100, 200, startStamp, startStamp + 1)) {
            System.out.println(atomicReference.getReference());//100
            System.out.println(Thread.currentThread().getName() + "线程修改失败");
        }
    },"t2").start();
}

(三)CAS 与 synchronized

  • synchronized 是从悲观的角度出发:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程),因此 synchronized 也称之为悲观锁,ReentrantLock 也是一种悲观锁,性能较差

  • CAS 是从乐观的角度出发:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。如果别人修改过,则获取现在最新的值,如果别人没修改过,直接修改共享数据的值,CAS 这种机制也称之为乐观锁,综合性能较好

(四)CAS操作示例

假设有两个线程(Thread-1和Thread-2)同时尝试通过CAS操作将AtomicInteger的值从0更新为1

import java.util.concurrent.atomic.AtomicInteger;

public class CASExample {
    public static void main(String[] args) {
        AtomicInteger atomicInt = new AtomicInteger(0); // 初始值为0
        
        // Thread-1尝试CAS(0→1)
        new Thread(() -> {
            boolean success = atomicInt.compareAndSet(0, 1); // 预期值0,新值1
            System.out.println("Thread-1: " + success); // 输出true
        }).start();

        // Thread-2尝试CAS(0→1)
        new Thread(() -> {
            boolean success = atomicInt.compareAndSet(0, 1); // 预期值0,新值1
            System.out.println("Thread-2: " + success); // 输出false
        }).start();
    }
}

流程图:

二、Atomic

(一)常用API

常见原子类:AtomicInteger、AtomicBoolean、AtomicLong

构造方法:

  • public AtomicInteger():初始化一个默认值为 0 的原子型 Integer

  • public AtomicInteger(int initialValue):初始化一个指定值的原子型 Integer

常用API:

方法作用
public final int get()获取 AtomicInteger 的值
public final int getAndIncrement()以原子方式将当前值加 1,返回的是自增前的值
public final int incrementAndGet()以原子方式将当前值加 1,返回的是自增后的值
public final int getAndSet(int value)以原子方式设置为 newValue 的值,返回旧值
public final int addAndGet(int data)以原子方式将输入的数值与实例中的值相加并返回 实例:AtomicInteger 里的 value

(二)原理分析

AtomicInteger 原理:自旋锁 + CAS 算法

CAS 算法:有 3 个操作数(内存值 V, 旧的预期值 A,要修改的值 B)

  • 当旧的预期值 A == 内存值 V 此时可以修改,将 V 改为 B

  • 当旧的预期值 A != 内存值 V 此时不能修改,并重新获取现在的最新值,重新获取的动作就是自旋

分析 getAndSet 方法:

  • AtomicInteger:

public final int getAndSet(int newValue) {
    /**
    * this: 		当前对象
    * valueOffset:	内存偏移量,内存地址
    */
    return unsafe.getAndSetInt(this, valueOffset, newValue);
}

valueOffset:偏移量表示该变量值相对于当前对象地址的偏移,Unsafe 就是根据内存偏移地址获取数据

valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
//调用本地方法   -->
public native long objectFieldOffset(Field var1);
  • unsafe 类:
// val1: AtomicInteger对象本身,var2: 该对象值得引用地址,var4: 需要变动的数
public final int getAndSetInt(Object var1, long var2, int var4) {
    int var5;
    do {
        // var5: 用 var1 和 var2 找到的内存中的真实值
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var4));

    return var5;
}

var5:从主内存中拷贝到工作内存中的值(每次都要从主内存拿到最新的值到本地内存),然后执行 compareAndSwapInt() 再和主内存的值进行比较,假设方法返回 false,那么就一直执行 while 方法,直到期望的值和真实值一样,修改数据

  • 变量 value 用 volatile 修饰,保证了多线程之间的内存可见性,避免线程从工作缓存中获取失效的变量

private volatile int value

CAS 必须借助 volatile 才能读取到共享变量的最新值来实现比较并交换的效果

分析 getAndUpdate 方法:

  • getAndUpdate:

public final int getAndUpdate(IntUnaryOperator updateFunction) {
    int prev, next;
    do {
        prev = get();	//当前值,cas的期望值
        next = updateFunction.applyAsInt(prev);//期望值更新到该值
    } while (!compareAndSet(prev, next));//自旋
    return prev;
}

函数式接口:可以自定义操作逻辑

AtomicInteger a = new AtomicInteger();
a.getAndUpdate(i -> i + 10);
  • compareAndSet:
public final boolean compareAndSet(int expect, int update) {
    /**
    * this: 		当前对象
    * valueOffset:	内存偏移量,内存地址
    * expect:		期望的值
    * update: 		更新的值
    */
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

(三)原子引用

原子引用:对 Object 进行原子操作,提供一种读和写都是原子性的对象引用变量

原子引用类:AtomicReference、AtomicStampedReference、AtomicMarkableReference

AtomicReference 类:

  • 构造方法:AtomicReference<T> atomicReference = new AtomicReference<T>()

  • 常用 API:

    • public final boolean compareAndSet(V expectedValue, V newValue):CAS 操作

    • public final void set(V newValue):将值设置为 newValue

    • public final V get():返回当前值

public class AtomicReferenceDemo {
    public static void main(String[] args) {
        Student s1 = new Student(33, "z3");
        
        // 创建原子引用包装类
        AtomicReference<Student> atomicReference = new AtomicReference<>();
        // 设置主内存共享变量为s1
        atomicReference.set(s1);

        // 比较并交换,如果现在主物理内存的值为 z3,那么交换成 l4
        while (true) {
            Student s2 = new Student(44, "l4");
            if (atomicReference.compareAndSet(s1, s2)) {
                break;
            }
        }
        System.out.println(atomicReference.get());
    }
}

class Student {
    private int id;
    private String name;
    //。。。。
}

(四)原子数组

原子数组类:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray

AtomicIntegerArray 类方法:

/**
*   i		the index
* expect 	the expected value
* update 	the new value
*/
public final boolean compareAndSet(int i, int expect, int update) {
    return compareAndSetRaw(checkedByteOffset(i), expect, update);
}

(五)原子更新器

原子更新器类:AtomicReferenceFieldUpdater、AtomicIntegerFieldUpdater、AtomicLongFieldUpdater

利用字段更新器,可以针对对象的某个域(Field)进行原子操作,只能配合 volatile 修饰的字段使用,否则会出现异常 IllegalArgumentException: Must be volatile type

常用 API:

  • static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> c, String fieldName):构造方法

  • abstract boolean compareAndSet(T obj, int expect, int update):CAS

public class UpdateDemo {
    private volatile int field;
    
    public static void main(String[] args) {
        AtomicIntegerFieldUpdater fieldUpdater = AtomicIntegerFieldUpdater
            		.newUpdater(UpdateDemo.class, "field");
        UpdateDemo updateDemo = new UpdateDemo();
        fieldUpdater.compareAndSet(updateDemo, 0, 10);
        System.out.println(updateDemo.field);//10
    }
}

(六)原子累加器

原子累加器类:LongAdder、DoubleAdder、LongAccumulator、DoubleAccumulator

LongAdder 和 LongAccumulator 区别:

相同点:

  • LongAddr 与 LongAccumulator 类都是使用非阻塞算法 CAS 实现的

  • LongAddr 类是 LongAccumulator 类的一个特例,只是 LongAccumulator 提供了更强大的功能,可以自定义累加规则,当accumulatorFunction 为 null 时就等价于 LongAddr

不同点:

  • 调用 casBase 时,LongAccumulator 使用 function.applyAsLong(b = base, x) 来计算,LongAddr 使用 casBase(b = base, b + x)

  • LongAccumulator 类功能更加强大,构造方法参数中

    • accumulatorFunction 是一个双目运算器接口,可以指定累加规则,比如累加或者相乘,其根据输入的两个参数返回一个计算值,LongAdder 内置累加规则

    • identity 则是 LongAccumulator 累加器的初始值,LongAccumulator 可以为累加器提供非0的初始值,而 LongAdder 只能提供默认的 0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值