【JAVA并发】CAS(第一弹)

发布于:2023-01-18 ⋅ 阅读:(437) ⋅ 点赞:(0)

一、什么是CAS

CAS(Compare And Swap/Set),比较并交换。是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS 算法涉及到三个操作数:

  • 需要读写的内存值V
  • 进行比较的值A
  • 拟写入的新值B

当且仅当 V 的值等于 A 时, CAS 通过原子方式用新值 B 来更新 V 的值, 否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。工作原理流程图如下:

【注】CAS是系统原语,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行的过程中不允许被打断,因此不会造成所谓的数据不一致问题,是线程安全的。 

二、CAS在JUC下的应用

jdk1.8下 java.util.concurrent.atomic包中有17个类,如下

1、基本类型原子类:使用原子的方式更新基本类型

  • AtomicInteger:int类型原子类
  • AtomicLong:long类型原子类
  • AtomicBoolean:boolean类型原子类

以AtomicInteger为例:

 public static void main(String[] args) {

        AtomicInteger atomicInteger1 = new AtomicInteger(2022);
        AtomicInteger atomicInteger2 = new AtomicInteger(2022);        
        //比较并交换
        atomicInteger1.compareAndSet(2021, 2023);
        atomicInteger2.compareAndSet(2022, 2023);
        System.out.println("1修改后:" + atomicInteger1);
        System.out.println("2修改后:" + atomicInteger2);
    }
打印结果:
1修改后:2022
2修改后:2023

2、数组类型原子类:使用原子的方式更新数组里的某个元素,可以确保修改数组中数据的线程安全性。

  • AtomicIntegerArray:整形数组原子操作类
  • AtomicLongArray:长整形数组原子操作类
  • AtomicReferenceArray:引用类型数组原子操作类

以AtomicIntegerArray为例:

    public static void main(String[] args) {
        int[] arr = new int[]{1,2,3,4,5};
        AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(arr);
        atomicIntegerArray.compareAndSet(2,3,100);
        System.out.println("AtomicIntegerArray修改后:" + atomicIntegerArray);
    }
打印结果:
AtomicIntegerArray修改后:[1, 2, 100, 4, 5]

3、引用类型原子类:基本类型原子类只能更新一个变量,如果需要原子更新多个变量,需要使用引用类型原子类

  • AtomicReference:引用类型原子类
  • AtomicStampedReference:原子更新引用类型里的字段原子类
  • AtomicMarkableReference:原子更新带有标记位的引用类型

以 AtomicReference 为例:10个人每人向张三账户汇款100元

public class AtomicReferenceText {

    private static AtomicReference<BankCard> atomicReference = new AtomicReference<>(new BankCard("张三", 100));
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                BankCard bankCard;
                BankCard newBankCard;
                do {
                    bankCard = atomicReference.get();
                    newBankCard = new BankCard(bankCard.getAccount(), bankCard.getAmount() + 100);
                } while (!atomicReference.compareAndSet(bankCard, newBankCard));
                System.out.println("线程:" + Thread.currentThread().getName() + " 汇款到账户成功,当前金额:" + newBankCard.getAmount());
            }).start();
        }
    }
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class BankCard {
    //账户
    private String account;
    //金额
    private int amount;
}

结果打印:
线程:Thread-0 汇款到账户成功,当前金额:300
线程:Thread-8 汇款到账户成功,当前金额:600
线程:Thread-2 汇款到账户成功,当前金额:500
线程:Thread-6 汇款到账户成功,当前金额:800
线程:Thread-5 汇款到账户成功,当前金额:400
线程:Thread-1 汇款到账户成功,当前金额:200
线程:Thread-7 汇款到账户成功,当前金额:900
线程:Thread-3 汇款到账户成功,当前金额:700
线程:Thread-4 汇款到账户成功,当前金额:1100
线程:Thread-9 汇款到账户成功,当前金额:1000

 4、对象的属性修改原子类:如果需要原子更新某个类里的某个字段时,需要用到对象的属性修改原子类

  • AtomicIntegerFieldUpdater:原子更新整形字段的值
  • AtomicLongFieldUpdater:原子更新长整形字段的值
  • AtomicReferenceFieldUpdater:原子更新应用类型字段的值

5、分散热点的高性能原子更新:jdk1.8中引入了5个类

  • Striped64:高并发原子累加器
  • LongAdder:处理 long 类型分段锁并发,性能比 AtomicLong 好
  • LongAccumulator:用于自定义运算规则场景下的多线程并发场合
  • DoubleAdder:处理 double 类型分段锁并发
  • DoubleAccumulator:用法与LongAccumulator基本相同,只是处理类型不同

三、CAS的原理

1、Unsafe类

Unsafe 是 CAS 的核心类,由于 Java 方法无法直接访问底层系统,而是通过本地 (native) 方法来访问,Unsafe 相当于一个后门。基于该类可以操作特定内存的数据。Unsafe 类存在于sun.misc (jre文件夹下rt.jar) 包中,其内部方法操作可以像C的指针一样直接操作内存,因为 java 中的 CAS 操作的执行依赖于 Unsafe 类的方法。
【注】Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的所有方法都可以直接调用操作系统。

2、变量 valueOffset,表示该变量值在内存中的偏移地址(内存地址),因为 Unsafe 就是根据内存偏地址获取数据的

3、变量 value 用 volatile 修饰,保证了多线程之间的内存可见性。

源码如下:

   public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;


  
   public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
}

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到