• 并发编程(五)原子类


    一、前言

      原子类主要用于并发编程里操作原子数据时使用到,位于util.concurrent.atomic包下。

      我们可以通过下图看到,在JDK中已经定义了很多个原子类:

      我们可以根据其功能把常用的一些原子类进行分类:

    • 原子更新基本数据类型:
      • AtomicBoolean 原子更新布尔类型
      • AtomicInteger 原子更新整型
      • AtomicLong 原子更新长整型
    • 原子更新数组类型:
      • AtomicIntegerArray :原子更新整型数组里的元素
      • AtomicLongArray 原子更新长整型数组里的元素
      • AtomicReferenceArray 原子更新引用类型数组里的元素
    • 原子更新引用数据类型:
      • AtomicReference :原子更新引用类型
      • AtomicReferenceFieldUpdater 原子更新引用类型的字段
      • AtomicMarkableReferce :原子更新带有标记位的引用类型,可以使用构造方法更新一个布尔类型的标记位和引用类型
    • 原子更新字段类型:
      • AtomicIntegerFieldUpdater :原子更新整型的字段的更新器
      • AtomicLongFieldUpdater 原子更新长整型字段的更新器
      • AtomicStampedFieldUpdater 原子更新带有版本号的引用类型

    二、常用方法整理

      由于类中许多方法功能类似,这里进行分类列举,把常用的方法写出来先了解,源码部分后面有机会再详细研究,第一阶段,先知道怎么用~

    原子更新基本数据类型

      以 AtomicInteger 为例,常用的方法有:

    • int addAndGet(int delta) : 以原子的方式将输入的数值与实例中的值相加,并返回结果。
    • boolean compareAndSet(int expect, int update) :如果输入的值等于预期值,则以原子方式将该值设置为输入的值。
    • int getAndIncrement() :以原子的方式将当前值加 1。PS:这里返回的是自增前的值,也就是旧值
    • void lazySet(int newValue) :最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
    • int getAndSet(int newValue) :以原子的方式设置为newValue,并返回旧值。

      写一个测试类来简单看下这些方法怎么使用:

    /**
     * 原子更新基本类型测试类【AtomicInteger为例】
     *
     * @author 有梦想的肥宅
     */
    public class AtomicTest {
    
        public static AtomicInteger ai = new AtomicInteger(1);
    
        public static void main(String[] args) {
            System.out.println("=====  1、先原子相加,再返回结果  =====");
            System.out.println(ai.addAndGet(2));
    
            System.out.println("=====  2、输入当前值【第一个参数】,比较成功后返回true并设置值为【第二个参数】,否则返回false   =====");
            System.out.println(ai.compareAndSet(3, 2));
    
            System.out.println("=====  3、获取旧值并+1     =====");
            System.out.println(ai.getAndIncrement());
    
            System.out.println("=====  4、后置赋值,赋值期间可能会被其他线程获取到旧值     =====");
            ai.lazySet(7);
            System.out.println(ai.get());
    
            System.out.println("=====  5、原子方式设置为新的值,并返回旧的值     =====");
            System.out.println(ai.getAndSet(10));
    
            System.out.println("=====  6、获取当前原子数值     =====");
            System.out.println(ai.get());
        }
    
    }

      原理:

    •   
    • 底层代码的compareAndSwapInt()方法,使用了CAS原理比较和交换去控制。【CAS详解看这里

    原子更新数组类型

        以 AtomicIntegerArray 为例,常用的方法有:

    • set(int index,int value) :将位置 i 的元素设置为给定值,默认值为0
    • get(int index) : 获取索引为index的元素值。
    • addAndGet(int index,int value) :以原子方式先对给定下标加上特定的值,再获取相加后的值
    • incrementAndGet(int index) :以原子方式先对下标加1再获取值
    • compareAndSet(int i, int expect, int update) : 如果当前值等于预期值,则以原子方式将数组位置 i 的元素设置为update值。

      写一个测试类来简单看下这些方法怎么使用:

    /**
     * 原子更新数组类型测试类【AtomicIntegerArray为例】
     *
     * @author 有梦想的肥宅
     */
    public class AtomicTest2 {
    
        public static int[] value = new int[]{1, 2, 3, 4, 5};
        public static AtomicIntegerArray aiArray = new AtomicIntegerArray(value);
    
        public static void main(String[] args) {
            System.out.println("=====  1、将位置 i 的元素设置为给定值,默认值为0  =====");
            aiArray.set(0, 10);
            System.out.println(aiArray.get(0));
    
            System.out.println("=====  2、以原子方式先对给定下标加上特定的值,再获取相加后的值  =====");
            System.out.println(aiArray.addAndGet(0, 2));
    
            System.out.println("=====  3、如果当前值【第一个参数】 == 预期值,则以原子方式将位置 i 的元素设置为给定的更新值【第二个参数】  =====");
            System.out.println(aiArray.compareAndSet(0, 12, 6));
    
            System.out.println("=====  4、以原子方式先对下标加1再获取值  =====");
            System.out.println(aiArray.incrementAndGet(0));
        }
    
    }

    原子更新引用数据类型

      原子更新基本类型,只能更新一个值。如果更新多个值,比如更新一个对象里的值,那么就要用原子更新引用类型提供的类,我们直接写一个测试类来看看怎么使用:

    /**
     * 原子更新引用类型测试类
     *
     * @author 有梦想的肥宅
     */
    public class AtomicTest3 {
    
        public static AtomicReference<User> aiReference = new AtomicReference<User>();
    
        public static void main(String[] args) {
            //1、原子引用内放入u1对象
            User u1 = new User("zh", 26, "男");
            aiReference.set(u1);
            System.out.println(aiReference.get().getName() + aiReference.get().getAge() + aiReference.get().getSex());
    
            //2、比较原子引用内的对象,如果相同则替换为新对象
            User u2 = new User("zh", 26, "女");
            aiReference.compareAndSet(u2, u2);
            System.out.println(aiReference.get().getName() + aiReference.get().getAge() + aiReference.get().getSex());
    
            //3、传入的预期对象【第一个参数】与当前原子引用类型内的对象完全一致时,则更新为新的对象
            User u3 = new User("wlx", 27, "女");
            aiReference.compareAndSet(u1, u3);
            System.out.println(aiReference.get().getName() + aiReference.get().getAge() + aiReference.get().getSex());
    
        }
    
        /**
         * 用户基本信息
         */
        @Data
        public static class User {
            private String name;
            private int age;
            private String sex;
    
            public User(String name, int age, String sex) {
                this.name = name;
                this.age = age;
                this.sex = sex;
            }
        }
    
    }

      原理:

    • 底层代码的compareAndSwapObject()方法,使用了CAS原理比较和交换去控制。【CAS详解看这里

    原子更新字段类型

      原子更新字段类型主要是用在更新一个对象里面的某个字段的场景,我们也是直接上代码来搞一波:

    /**
     * 原子更新字段类型测试类
     *
     * @author 有梦想的肥宅
     */
    public class AtomicTest4 {
        //创建原子更新器,并设置需要更新的对象类和对象的属性//AtomicIntegerFieldUpdater.newUpdater:第一个参数【对象的类型】,第二个参数【需要更新的字段】
        private static AtomicIntegerFieldUpdater<User> aiFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
    
        public static void main(String[] args) {
            //1、创建一个用户对象
            User u1 = new User("zh", 26, "男");
            //2、使用原子字段更新类更新年龄,+1
            System.out.println(aiFieldUpdater.getAndIncrement(u1));
            //3、输出更新后的年龄
            System.out.println(u1.getAge());
        }
    
    
        /**
         * 用户基本信息
         */
        @Data
        public static class User {
            private String name;
            volatile int age; //PS:需要被原子更新的字段不能设置为private,且要加上volatile关键字来修饰
            private String sex;
    
            public User(String name, int age, String sex) {
                this.name = name;
                this.age = age;
                this.sex = sex;
            }
        }
    
    }

    注意:

    • 1、需要被原子更新的字段不能设置为private
    • 2、需要被原子更新的字段要加上volatile关键字来修饰

    参考文章:

  • 相关阅读:
    Gym 100818F Irrational Roots (数学)
    学习总结 for循环--冒泡排序
    学习总结 for循环语句的应用
    学习总结 条件语句的应用
    学习总结 运算符了解与应用
    学习记录 彻底搞清 C#中a++与++a的区别
    学习总结 数据类型的应用与转换
    学习总结 数据类型
    学习总结 二进制转换与应用
    学习总结 vs软件简单了解
  • 原文地址:https://www.cnblogs.com/riches/p/13784136.html
Copyright © 2020-2023  润新知