并发编程(五)原子类

一、前言

  原子类主要用于并发编程里操作原子数据时使用到,位于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关键字来修饰

参考文章:

原文地址:https://www.cnblogs.com/riches/p/13784136.html