Java 原子操作类

概述

java.util.concurrent.atomic 包一共提供了 13 个类,属于 4 种类型的原子更新方式:原子更新基本数据类型、原子更新数组、原子更新引用、原子更新属性

原子更新基本类型

java.util.concurrent.atomic 包提供了以下 3 个类:

  • AtomicBoolean:原子更新布尔类型
  • AtomicInteger : 原子更新整型
  • AtomicLong:原子更新整型

以上三个类提供的方法几乎一样,下面只分析 AtomicInteger:

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

通过 getAndIncrement 方法来看看实现原理:

public final int getAndIncrement() {
    for (;;) {
        int current = get();  // 先取得 AtomicInteger 存储的数值
        int next = current + 1;  // 对当前数组加 1
        // CAS 操作更新,先检查当前数值是否等于 current,如果是则将 AtomicInteger 的当前数值更新成 next;如果不是,则返回 false,重新循环更新
        if (compareAndSet(current, next))  
            return current;  // 返回更新前的值
    }
}
public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

如何原子地更新其他的基本类型?
由于 java.util.concurrent.atomic 包的类都是使用 Unsafe 实现的,先看一下 Unsafe 的源码:

public native boolean compareAndSwapObject(Object obj, long offset,Object expect, Object update);

public native boolean compareAndSwapLong(Object obj, long offset,long expect, long update);

public native boolean compareAndSwapInt(Object obj, long offset,int expect, int update);

通过 Unsafe 源码分析可知,只提供了 3 种 CAS 操作,分别是 compareAndSwapObject、compareAndSwapLong、compareAndSwapInt

我们再看一下 AtomicBoolean 类的实现:

public final boolean compareAndSet(boolean expect, boolean update) {
    int e = expect ? 1 : 0;  // 转换成 int 类型
    int u = update ? 1 : 0;  // 转换成 int 类型
    return unsafe.compareAndSwapInt(this, valueOffset, e, u);
}

通过以上代码可以发现,AtomicBoolean 先把 boolean 类型的参数转换成 int 类型,然后再调用 Unsafe 的 compareAndSwapInt 来进行 CAS 操作。因此,对于 char、float、double 类型的变量也可以用类似的思路实现

原子更新数组

java.util.concurrent.atomic 包提供了 3 个原子更新数组的类:

  • AtomicIntegerArray:原子更新整型数组里的元素
  • AtomicLongArray:原子更新长整型数组里的元素
  • AtomicReferenceArray:原子更新引用类型数组里的元素

AtomicIntegerArray 常用方法如下:

  • addAndGet(int i,int delta):以原子方式将输入值与数组中索引i的元素相加。
  • boolean compareAndSet(int i, int expect, int update):如果当前值等于预期值,则以原子方式将数组位置 i 的元素设置成 update 值
public class Test {
    static int[] value = new int[] {1,2};
    static AtomicIntegerArray ai = new AtomicIntegerArray(value);
    public static void main(String[] args){
        ai.getAndSet(0, 3);
        System.out.println(ai.get(0));
        System.out.println(value[0]);
    }
}

输出结果为:

3 
1

需要注意的是,数组 value 通过构造方法传递进去,然后 AtomicIntegerArray 会将当前数组复制一份,所以当 AtomicIntegerArray 对内部数组元素进行修改,不会影响传入的数组

原子更新引用类型

如果要原子更新多个变量,需要使用原子更新引用类型提供的类。java.util.concurrent.atomic 包提供了 3 个类:

  • AtomicReference:原子更新引用类型
  • AtomicReferenceFieldUpdater:原子更新引用类型里的字段
  • AtomicMarkableReference:原子更新带有标记为的引用类型。可以原子更新一个布尔类型的标记位和引用类型。构造方法是 AtomicMarkableReference(V initialRef, boolean initialMark)

AtomicReference 示例如下:

public class AtomicReferenceTest {
    static class User {
        private String name;
        private int old;
        public User(String name,int old){
            this.name = name;
            this.old = old;
        }
        public String getName() {
            return name;
        }
        public int getOld() {
            return old;
        }
    }

    public static AtomicReference atomicReference = new AtomicReference();

    public static void main(String[] args){
        AtomicReferenceTest.User user = new User("Tom", 15);
        atomicReference.set(user);
        User updateUser = new User("Jack",16);
        atomicReference.compareAndSet(user, updateUser);
        System.out.println(atomicReference.get().getName());
        System.out.println(atomicReference.get().getOld());
    }
}

输出结果:

jack 
16

原子更新字段类

如果需要原子更新某个类的字段时,需要使用原子更新字段类,java.util.concurrent.atomic 提供了3个类:

  • AtomicIntegerFieldUpdater:原子更新整型的字段的更新器
  • AtomicLongFieldUpdater:原子更新长整型的字段的更新器
  • AtomicStampedReference:原子更新带有版本号的引用类型。该类型将整数值与引用关联起来,可用于原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题

原子更新类的字段,需要两步:

  • 第一步:因为原子更新字段类都是抽象类,每次使用的时候必须使用静态方法 newUpdater() 创建一个更新器,并且需要设置想要更新的类和属性
  • 第二步:更新类的字段必须使用 public volatile 修饰

AtomicIntegerFieldUpdater 示例如下:

public class AtomicIntegerFieldUpdaterTest {
    static class User {
        private String name;
        private int old;
        public User(String name, int old){
            this.name = name;
            this.old = old;
        }
        public String getName() {
            return name;
        }
        public int getOld() {
            return old;
        }
    }

    private static AtomicIntegerFieldUpdater aifu = AtomicIntegerFieldUpdater.newUpdater(User.class, "old");

    public static void main(String[] args){
        User user = new User("Tom", 15);
        System.out.println(aifu.getAndIncrement(user));  // old 加 1,但是仍然会输出 15
        System.out.println(aifu.get(user));  // 16
    }
}

输入结果如下;

15 
16

你可能感兴趣的:(Java 原子操作类)