美文网首页
Java 原子操作类

Java 原子操作类

作者: 杰哥长得帅 | 来源:发表于2018-04-29 16:47 被阅读69次

    概述

    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<User> atomicReference = new AtomicReference<User>();
    
        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<User> 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 原子操作类

          本文链接:https://www.haomeiwen.com/subject/yunolftx.html