Java中的Atomic包使用指南

引言
Java从JDK1.5开始提供了java.util.concurrent.atomic包,方便程序员在多线程环境下,无锁的进行原子操做。底层使用了Unsafe类的native方法,提供了硬件级别的原子操做,可是不一样的CPU架构可能提供的原子指令不同,也有可能须要某种形式的内部锁,因此该包下的方法不能绝对保证线程不被阻塞。java

Atomic包介绍
在Atomic包里一共有17个类,四种原子更新方式,分别是原子更新基本类型,原子更新数组,原子更新引用和原子更新字段。Atomic包里的类基本都是使用Unsafe的CAS实现的包装类。程序员

原子更新基本类型类
用于经过原子的方式更新基本类型,Atomic包提供了如下三个类:数组

AtomicBoolean:原子更新布尔类型。
AtomicInteger:原子更新整型。
AtomicLong:原子更新长整型。
AtomicInteger的经常使用方法以下:安全

int addAndGet(int delta) :以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果
boolean compareAndSet(int expect, int update) :若是输入的数值等于预期值,则以原子方式将该值设置为输入的值。
int getAndIncrement():以原子方式将当前值加1,注意:这里返回的是自增前的值。
void lazySet(int newValue):最终会设置成newValue,使用lazySet设置值后,可能致使其余线程在以后的一小段时间内仍是能够读到旧的值。关于该方法的更多信息能够参考并发网翻译的一篇文章《AtomicLong.lazySet是如何工做的?》
int getAndSet(int newValue):以原子方式设置为newValue的值,并返回旧值。
AtomicInteger例子代码以下:多线程

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerTest {

    static AtomicInteger ai = new AtomicInteger(1);

    public static void main(String[] args) {
        System.out.println(ai.getAndIncrement());
        System.out.println(ai.get());
    }

}

输出架构

1
2

餐后甜点
Atomic包提供了三种基本类型的原子更新,可是Java的基本类型里还有char,float和double等。那么问题来了,如何原子的更新其余的基本类型呢?Atomic包里的类基本都是使用Unsafe实现的,让咱们一块儿看下Unsafe的源码,发现Unsafe只提供了三种CAS方法,compareAndSwapObject,compareAndSwapInt和compareAndSwapLong,再看AtomicBoolean源码,发现其是先把Boolean转换成整型,再使用compareAndSwapInt进行CAS,因此原子更新double也能够用相似的思路来实现。并发

原子更新数组类
经过原子的方式更新数组里的某个元素,Atomic包提供了如下三个类:函数

AtomicIntegerArray:原子更新整型数组里的元素。
AtomicLongArray:原子更新长整型数组里的元素。
AtomicReferenceArray:原子更新引用类型数组里的元素。
AtomicIntegerArray类主要是提供原子的方式更新数组里的整型,其经常使用方法以下高并发

int addAndGet(int i, int delta):以原子方式将输入值与数组中索引i的元素相加。
boolean compareAndSet(int i, int expect, int update):若是当前值等于预期值,则以原子方式将数组位置i的元素设置成update值。
实例代码以下:性能

public class AtomicIntegerArrayTest {

    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

AtomicIntegerArray类须要注意的是,数组value经过构造方法传递进去,而后AtomicIntegerArray会将当前数组复制一份,因此当AtomicIntegerArray对内部的数组元素进行修改时,不会影响到传入的数组。

原子更新引用类型
原子更新基本类型的AtomicInteger,只能更新一个变量,若是要原子的更新多个变量,就须要使用这个原子更新引用类型提供的类。Atomic包提供了如下三个类:

AtomicReference:原子更新引用类型。
AtomicReference的使用例子代码以下:

import java.util.concurrent.atomic.AtomicReference;
import static java.lang.System.out;

public class AtomicReferenceTest {
    public static void main(String[] args) {
        AtomicReference<User> atomicUserRef = new AtomicReference();
        User user = new User("last", 15);
        atomicUserRef.set(user);
        User updateUser = new User("soul", 17);
        atomicUserRef.compareAndSet(user, updateUser);
        out.printf("name:%s,age:%d \n",atomicUserRef.get().getName(),atomicUserRef.get().getAge());
    }

     static class User {
        private String name;
        private int age;

        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public int getAge() {
            return age;
        }
    }
}

输出

name:soul,age:17

原子更新字段类
若是咱们只须要某个类里的某个字段,那么就须要使用原子更新字段类AtomicReferenceFieldUpdater。
要想使用原子更新字段类须要注意如下问题:

  1. 更新类的属性必须使用public volatile进行修饰;
  2. 更新类的属性不能是基本类型,必须是包装类型或者其余类型,好比:Integer,String等;
  3. 一个更新器能够更新同一类型的多个对象;

AtomicReferenceFieldUpdater不能更新原子类型属性,原子类型属性要用AtomicIntegerFieldUpdater,它要注意的问题同上,除了第二条对属性的限制。它要求属性必须是int,而不能是Integer.
和AtomicIntegerFieldUpdater相似的是AtomicLongFieldUpdater,它们是原子更新基本类型字段的更新器。

示例代码以下:

public class FieldUpdaterTest {
    public static void main(String[] args) {
        //定义一个更新器,指定更新的对象,字段类型以及字段名字
        AtomicReferenceFieldUpdater updater = AtomicReferenceFieldUpdater.newUpdater(User.class, Integer.class, "age");
        //要被更新的对象
        User user1 = new User("user1", 1);
        //更新对象的属性
        updater.compareAndSet(user1, user1.age, 3);
        System.out.println(user1.age);

        //另外一个要被更新的对象
        User user2 = new User("user2", 1);
        //更新对象的属性
        updater.compareAndSet(user2, user2.age, 4);
        System.out.println(user2.age);

        //Integer类型属性更新器,它的属性必须是原子类型,而不能是包装类型
        AtomicIntegerFieldUpdater integerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(User.class, "old");
        //要被更新的对象
        User user3 = new User("user3", 1, 4);
        //更新对象的属性
        integerFieldUpdater.compareAndSet(user3, user3.old, 5);
        System.out.println(user3.old);

    }

    static class User {
        public volatile String userName;
        public volatile Integer age;
        public volatile int old;

        public User(String userName, int age) {
            this.userName = userName;
            this.age = age;
        }

        public User(String userName, int age, int old) {
            this.userName = userName;
            this.age = age;
            this.old = old;
        }
    }
}

输出

10
11

AtomicReference经过volatile和Unsafe提供的CAS函数实现原子操做。 自旋+CAS的无锁操做保证共享变量的线程安全。
可是CAS操做可能存在ABA问题。AtomicStampedReference和AtomicMarkableReference的出现就是为了解决这问题。
AtomicStampedReference

构造方法中initialStamp(时间戳)用来惟一标识引用变量,在构造器内部,实例化了一个Pair对象,Pair对象记录了对象引用和时间戳信息,采用int做为时间戳,实际使用的时候,要保证时间戳惟一(通常作成自增的),若是时间戳若是重复,还会出现ABA的问题。

AtomicStampedReference中的每个引用变量都带上了pair.stamp这个时间戳,这样就能够解决CAS中的ABA的问题。

示例代码:

public class StampedReferenceTest {
    public static void main(String[] args) throws InterruptedException {
        //初始值,也能够用自定义对象
        int initialRef = 1;
        //初始版本
        int initialStamp = 1;
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference(initialRef, initialStamp);
        System.out.println("initial ref=" + atomicStampedReference.getReference() + " and stamp=" + atomicStampedReference.getStamp());
        //指望值
        final int expectedRef = atomicStampedReference.getReference();
        //新值
        int newRef = atomicStampedReference.getReference() + 10;
        //指望版本
        final int expectedStamp = atomicStampedReference.getStamp();
        //新版本
        int newStamp = atomicStampedReference.getStamp() + 1;
        Thread t1 = new Thread(() -> System.out.println("success:"
                + atomicStampedReference.compareAndSet(expectedRef, newRef, expectedStamp, newStamp)));

        t1.start();
        t1.join();
        System.out.println("ref=" + atomicStampedReference.getReference() + " and stamp=" + atomicStampedReference.getStamp());
        Thread t2 = new Thread(() -> System.out.println("success:"
                + atomicStampedReference.compareAndSet(expectedRef, newRef, expectedStamp, newStamp)));

        t2.start();
        t2.join();
        System.out.println("ref=" + atomicStampedReference.getReference() + " and stamp=" + atomicStampedReference.getStamp());
    }
}

AtomicMarkableReference
AtomicStampedReference能够知道,引用变量中途被更改了几回。有时候,咱们并不关心引用变量更改了几回,只是单纯的关心是否更改过,因此就有了AtomicMarkableReference。

AtomicMarkableReference的惟一区别就是再也不用int标识引用,而是使用boolean变量——表示引用变量是否被更改过。

示例代码

public class AtomicMarkableReferenceTest {
    public static void main(String[] args) throws InterruptedException {
        //初始值,也能够用自定义对象
        int initialRef = 1;
        //初始标识
        boolean initialMark = false;
        AtomicMarkableReference<Integer> markableReference = new AtomicMarkableReference(initialRef, initialMark);
        System.out.println("initial ref=" + markableReference.getReference() + " and isMarked=" + markableReference.isMarked());
        //指望值
        int expectedRef = markableReference.getReference();
        /**
         * 并发过程当中t1,t2两个线程的指望值都是expectedRef,把值加10
         * t1,t2两个线程的指望标志都是false,操做成功后置为true
         * t1成功后标志变为true,因此与t2指望值不符,操做失败
         */
        Thread t1 = new Thread(() -> System.out.println("success:"
                + markableReference.compareAndSet(expectedRef, markableReference.getReference() + 10, false, true)));

        t1.start();
        t1.join();
        System.out.println("ref=" + markableReference.getReference() + " and isMarked=" + markableReference.isMarked());
        Thread t2 = new Thread(() -> System.out.println("success:"
                + markableReference.compareAndSet(expectedRef, markableReference.getReference() + 10, false, false)));

        t2.start();
        t2.join();
        System.out.println("ref=" + markableReference.getReference() + " and isMarked=" + markableReference.isMarked());
    }    
}

DoubleAccumulator、LongAccumulator、DoubleAdder、LongAdder是JDK1.8新增的部分,是对AtomicLong等类的改进。好比LongAccumulator与LongAdder在高并发环境下比AtomicLong更高效。本文以LongAdder为例,学习这些类。

API中是这么介绍的:LongAdder中会维护一组(一个或多个)变量,这些变量加起来就是要以原子方式更新的long型变量。当更新方法add(long)在线程间竞争时,该组变量能够动态增加以减缓竞争。方法sum()返回当前在维持总和的变量上的总和。与AtomicLong相比,LongAdder更多地用于收集统计数据,而不是细粒度的同步控制。在低并发环境下,二者性能很类似。但在高并发环境下,LongAdder有着明显更高的吞吐量,可是有着更高的空间复杂度。
实现原理
与其余原子类同样,LongAdder也是基于CAS实现的。

LongAdder能够代替AtomicLong吗

固然不能。在上面已经提到,与AtomicLong相比,LongAdder更多地用于收集统计数据,而不是细粒度的同步控制。并且,LongAdder只提供了add(long)和decrement()方法,想要使用cas方法仍是要选择AtomicLong。

DoubleAdder、LongAccumulator、DoubleAccumulator与LongAdder很类似,就很少作介绍了。

相关文章
相关标签/搜索