zoukankan      html  css  js  c++  java
  • 基础巩固篇 —— 对CAS的理解

    一、CAS是什么

    比较后交换,为了保证原子性而进行的比较和交换。

    二、CAS的使用

    前面说到volatile关键字是不保证原子性的,为了满足轻量级的JMM原则,可以通过volatile + CAS实现轻量级的JMM原则(保证数据可见性、保证原子性、禁止指令重排以保证有序性原则)。例如:

    public class VolatileDemo {
        // 定义volatile修饰的原子包装类
        public volatile AtomicInteger number = new AtomicInteger();
        //  实现原子整型包装类的自增i++
        public void atomicDemo() {
            number.getAndIncrement();
        }
    }
        /**
         * 测试20个线程各执行1000次自增后结果
         */
        @Test
        public void atomicDemo() {
            VolatileDemo volatileDemo = new VolatileDemo();
            for (int i = 0; i < 20; i++) {
                new Thread(() -> {
                    for (int j = 0; j < 1000; j++) {
                        volatileDemo.atomicDemo();
                    }
                }, String.valueOf(i)).start();
            }
            // 等自建的所有线程执行完成后再执行以下代码,因为程序默认存在main线程和GC垃圾回收线程, 故>2
            while (Thread.activeCount() > 2) {
                Thread.yield();
            }
            System.out.println(volatileDemo.number.get());
        }
    

    三、CAS原理

    在AtomicInteger原子整型包装类方法中,使用的关键class是Unsafe,这个类中的方法是由native修饰的,这是jdk与计算机系统之间数据操作的约定后门,类比Thread中的start方法的底层。这些类存在于jdk本身自带的rt.jar中。

    四、CAS缺点

    在AtomicInteger中存在方法compareAndSet

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

    参数expect是第一次取出的数值(线程从主内存copy到自己的工作内存中),update是在进行数据操作后的数值。当主内存中的数值与expect相同,则将主内存中的数值更新为update数值,并通知其他线程保证可见性,如果不同则不进行更新。这样只关注取出和结果比对的过程,有一个明显的缺陷,那就是ABA问题,ABA问题就是取出时是A,中间不管被其他线程更新多少次,只要在当前线程结果比对之前再变为A,那么就算比对成功,当前线程就还会将其数值认为一直没有变化,对其进行更新。

    五、CAS实例

    除了AtomicInteger之外,jdk还提供了不同类型的原子型包装类

    // 整型原子包装类,默认为0
    AtomicInteger atomicInteger = new AtomicInteger();
    // 布尔原子包装类,默认返回false
    AtomicBoolean atomicBoolean = new AtomicBoolean();
    // 自定义类原子包装
    AtomicReference<Object> atomicReference = new AtomicReference<>();
    
    AtomicLong atomicLong = new AtomicLong();
    // 带有版本号的自定义原子包装类,初始化需要提供版本号
    AtomicStampedReference<Object> atomicStampedReference = new AtomicStampedReference<>(null, 1);
    

    六、ABA问题的解决

    使用AtomicStampedReference自定义版本原子包装类可以解决ABA问题。原理是,在存到主内存时定义一个stamped版本号,copy值到线程工作内存时连带着版本号,而主内存中的值每更新一次,版本号也跟着自增。当前线程操作完值后进行CAS(比较并交换),此时需要比较值和版本号是否都一致,这样就避免了中间过程中值更新问题。

  • 相关阅读:
    Redis3.2集群部署安装
    熟悉SQL Server 数据类型
    泛型的参数简介和参数约束
    流(Stream)与文件流(FileStream)
    C#提取双引号中的字符串
    数组,一维数组,二维数组,交错数组
    结构,枚举
    C#中唯一的三元运算符
    位运算
    异常处理语句
  • 原文地址:https://www.cnblogs.com/zzb-yp/p/15031231.html
Copyright © 2011-2022 走看看