2019年5月12日 -- 原子类

原子类

在并发编程中需要考虑线程安全的问题,比如:多线程同时对变量 a 执行 a++操作,使用Synchronized可以达到线程安全的目的,但是synchronized采用的是悲观锁策略,并不是特别高效的一种解决方案。JDK1.5开始,在java.util.concurrent.atomic 包中提供了一系列操作简单、性能高效、并能保证线程安全的原子类,这些类都是采用乐观锁策略去原子更新数据,具体实现是基于CAS操作。

什么是CAS?

CAS的全称是Compare-And-Swap,意思是比较后交换。CAS比较交换的过程可以通俗的理解为CAS(V,O,N),包含三个值分别为:V 内存地址存放的实际值;O 预期的值(旧值);N 更新的新值。当V和O相同时,也就是线程工作内存缓存的值和主内存中实际的值相同,可以将新值N赋值给V。反之,V和O不相同则表明该值已经被其它线程改过了,所以不能将新值N赋给V。

CAS的底层实现需要硬件指令集的支撑,在JDK1.5后虚拟机才可以使用处理器提供的CMPXCHG指令实现。

CAS的问题

ABA问题

比如一个旧值A变成了B,然后又变成了A,这时CAS检查会发现旧值并没有变化,依然为A,但是实际上的确发生了变化,这就是ABA问题。原子类AtomicStampedReference 可以解决 ABA 问题,内部原理就是通过添加版本号,使原来的变化路径A->B->A变成了1A->2B->3A。

自旋时间过长

CAS是非阻塞同步,也就是说不会将线程挂起,会自旋(死循环)进行下一次尝试,如果自旋时间过长,对性能是很大的消耗。如果 JVM 能支持处理器提供的 pause 指令,那么效率会有一定的提升。pause 指令有两个作用,第一可以延迟流水线执行指令(de-pipeline),使 CPU 不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起 CPU 流水线被清空(CPU pipeline flush),从而提高 CPU 的执行效率。

只能保证一个共享变量的原子操作

当对一个共享变量执行操作时,可以使用循环 CAS 的方式来保证原子操作,但是对多个共享变量操作时,循环 CAS 就无法保证操作的原子性。原子类AtomicReference 可以把多个变量放在一个对象里来进行CAS操作,从而保证引用对象的原子性。

原子类的分类

按照原子更新方式,这些原子操作类大致可以分为四种:原子更新基本类型、原子更新数组、原子更新引用类型、原子更新对象属性。

基本数据类型原子类

基本数据类型原子类主要为以下几种:

  • AtomicBoolen: boolean类型原子类,可以用于标记。
  • AtomicInteger: int类型原子类,可以用于多线程计数器。
  • AtomicLong: long类型原子类

Atomic包下提供的更新基本类型变量的原子类源码大致一致,有兴趣的读者可以自行阅读。这里我们以AtomicInteger来进行讲解,具体代码如下:

AtomicInteger jdk1.8源码

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;

    /**
     * Creates a new AtomicInteger with the given initial value.
     *
     * @param initialValue the initial value
     */
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }

    /**
     * Creates a new AtomicInteger with initial value {@code 0}.
     */
    public AtomicInteger() {
    }/**
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     *
     * @param expect the expected value
     * @param update the new value
     * @return {@code true} if successful. False return indicates that
     * the actual value was not equal to the expected value.
     */
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

    /**
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     *
     * <p><a href="package-summary.html#weakCompareAndSet">May fail
     * spuriously and does not provide ordering guarantees</a>, so is
     * only rarely an appropriate alternative to {@code compareAndSet}.
     *
     * @param expect the expected value
     * @param update the new value
     * @return {@code true} if successful
     */
    public final boolean weakCompareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

    /**
     * Atomically increments by one the current value.
     * i++
     * @return the previous value
     */
    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

    /**
     * Atomically decrements by one the current value.
     * i--
     * @return the previous value
     */
    public final int getAndDecrement() {
        return unsafe.getAndAddInt(this, valueOffset, -1);
    }

    /**
     * Atomically adds the given value to the current value.
     * 
     * @param delta the value to add
     * @return the previous value
     */
    public final int getAndAdd(int delta) {
        return unsafe.getAndAddInt(this, valueOffset, delta);
    }

    /**
     * Atomically increments by one the current value.
     * ++i
     * @return the updated value
     */
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

    /**
     * Atomically decrements by one the current value.
     * --i
     * @return the updated value
     */
    public final int decrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
    }

    /**
     * Atomically adds the given value to the current value.
     *
     * @param delta the value to add
     * @return the updated value
     */
    public final int addAndGet(int delta) {
        return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
    }

基于CAS

public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

Example:使用AtomicInteger类型实现多线程安全的计数器。

public class OneWrapper {

    public static void main(String[] args) {
        // AtomicInteger 实现计数器
        AtomicInteger count = new AtomicInteger(0);
        for (int i = 0; i < 10; ++i) {
            CountThread thread = new CountThread(count);
            thread.start();
            try {
                // join 的含义:等待该线程终止。就是:当前线程等待子线程的终止。
                thread.join(0);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("count: " + count.get());
    }
}

class CountThread extends Thread{

    private AtomicInteger integer;

    CountThread(AtomicInteger integer){
        this.integer = integer;
    }

    @Override
    public void run(){
        integer.addAndGet(1);
        System.out.println("count thread running!");
    }
}

注意:AtomicBoolean的value的类型并不是布尔型,而是整型,它会把对应的布尔值转换成整型,true的时候对应1,false的时候对应0。
为什么是整型?看源码会发现,Unsafe提供的CompareAndSwap方法不支持boolean。

数组类型原子类

对于数组类型的原子类,在Java中,主要是通过原子的方式更新数组里面的某个元素,数组类型原子类主要有以下几种:

  • AtomicIntegerArray:Int数组类型原子类
  • AtomicLongArray:long数组类型原子类
  • AtomicReferenceArray:引用类型原子类(关于AtomicReferenceArray即引用类型原子类会在下文介绍)

AtomicIntegerArray与AtomicInteger的方法基本一致,只不过在AtomicIntegerArray的方法中会多一个指定数组元素的索引位。AtomicIntegerArray自己维护一个整型数组array,对数组元素的操作实质是对array的操作。

AtomicIntegerArray jdk1.8源码

public class AtomicIntegerArray implements java.io.Serializable {
    private static final long serialVersionUID = 2862133569453604235L;

    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final int base = unsafe.arrayBaseOffset(int[].class);
    private static final int shift;
    private final int[] array;

    static {
        int scale = unsafe.arrayIndexScale(int[].class);
        if ((scale & (scale - 1)) != 0)
            throw new Error("data type scale not a power of two");
        shift = 31 - Integer.numberOfLeadingZeros(scale);
    }

    private long checkedByteOffset(int i) {
        if (i < 0 || i >= array.length)
            throw new IndexOutOfBoundsException("index " + i);

        return byteOffset(i);
    }

    private static long byteOffset(int i) {
        return ((long) i << shift) + base;
    }

    /**
     * Creates a new AtomicIntegerArray of the given length, with all
     * elements initially zero.
     *
     * @param length the length of the array
     */
    public AtomicIntegerArray(int length) {
        array = new int[length];
    }

    /**
     * Creates a new AtomicIntegerArray with the same length as, and
     * all elements copied from, the given array.
     *
     * @param array the array to copy elements from
     * @throws NullPointerException if array is null
     */
    public AtomicIntegerArray(int[] array) {
        // Visibility guaranteed by final field guarantees
        this.array = array.clone();
    }

    /**
     * Returns the length of the array.
     *
     * @return the length of the array
     */
    public final int length() {
        return array.length;
    }

    /**
     * Gets the current value at position {@code i}.
     *
     * @param i the index
     * @return the current value
     */
    public final int get(int i) {
        return getRaw(checkedByteOffset(i));
    }/**
     * Atomically sets the element at position {@code i} to the given
     * updated value if the current value {@code ==} the expected value.
     *
     * <p><a href="package-summary.html#weakCompareAndSet">May fail
     * spuriously and does not provide ordering guarantees</a>, so is
     * only rarely an appropriate alternative to {@code compareAndSet}.
     *
     * @param i the index
     * @param expect the expected value
     * @param update the new value
     * @return {@code true} if successful
     */
    public final boolean weakCompareAndSet(int i, int expect, int update) {
        return compareAndSet(i, expect, update);
    }

    /**
     * Atomically increments by one the element at index {@code i}.
     *
     * @param i the index
     * @return the previous value
     */
    public final int getAndIncrement(int i) {
        return getAndAdd(i, 1);
    }

    /**
     * Atomically decrements by one the element at index {@code i}.
     *
     * @param i the index
     * @return the previous value
     */
    public final int getAndDecrement(int i) {
        return getAndAdd(i, -1);
    }

    /**
     * Atomically adds the given value to the element at index {@code i}.
     *
     * @param i the index
     * @param delta the value to add
     * @return the previous value
     */
    public final int getAndAdd(int i, int delta) {
        return unsafe.getAndAddInt(array, checkedByteOffset(i), delta);
    }

    /**
     * Atomically increments by one the element at index {@code i}.
     *
     * @param i the index
     * @return the updated value
     */
    public final int incrementAndGet(int i) {
        return getAndAdd(i, 1) + 1;
    }

    /**
     * Atomically decrements by one the element at index {@code i}.
     *
     * @param i the index
     * @return the updated value
     */
    public final int decrementAndGet(int i) {
        return getAndAdd(i, -1) - 1;
    }

    /**
     * Atomically adds the given value to the element at index {@code i}.
     *
     * @param i the index
     * @param delta the value to add
     * @return the updated value
     */
    public final int addAndGet(int i, int delta) {
        return getAndAdd(i, delta) + delta;
    }

Example:多线程同时修改数组中的某个元素。

public class TwoArray {

    public static void main(String[] args) {
        int[] a = {2,4,5};
        // 这里初始化对数组a的使用是深拷贝,在原子类中修改数组的元素值,不会影响到原有数组的值
        AtomicIntegerArray array = new AtomicIntegerArray(a);

        // 多个线程同时数组中下标为1的元素值
        int index = 1;
        for (int i = 0; i < 2; ++i){
            ArrayThread thread = new ArrayThread(array, index);
            thread.start();
            try {
                thread.join();
            } catch (InterruptedException ex){
                ex.printStackTrace();
            }
        }
        System.out.println(array.get(index));
        System.out.println(a[index]);
    }
}

class ArrayThread extends Thread{

    AtomicIntegerArray array;

    int index;

    public ArrayThread (AtomicIntegerArray array,int index){
        this.array = array;
        this.index = index;
    }

    @Override
    public void run(){
        for (int i = 0; i < 5; ++i){
            array.addAndGet(index, 1);
        }
    }
}

引用类型原子类

AtomicReference和AtomicInteger非常类似,不同之处就在于AtomicInteger是对整数的封装,而AtomicReference则对应普通的对象引用。也就是它可以保证你在修改对象引用时的线程安全性。

引用类型的主要原子类如下:

  • AtomicReference:
  • AtomicMarkableReference:用于更新带有标记位的引用类型。
  • AtomicStampedReference:用于更新带有版本号的引用类型,该类将版本号与引用类型关联起来,可以解决使用CAS进行原子更新时可能会出现的ABA问题。

关于引用类型的原子类,内部都调用的是CompareAndSwapObject()方法来实现CAS操作的。

AtomicJDK1.8源码

public class AtomicReference<V> implements java.io.Serializable {
    private static final long serialVersionUID = -1848883965231344442L;

    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicReference.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile V value;

    /**
     * Creates a new AtomicReference with the given initial value.
     *
     * @param initialValue the initial value
     */
    public AtomicReference(V initialValue) {
        value = initialValue;
    }

    /**
     * Creates a new AtomicReference with null initial value.
     */
    public AtomicReference() {
    }

    /**
     * Gets the current value.
     *
     * @return the current value
     */
    public final V get() {
        return value;
    }

    /**
     * Sets to the given value.
     *
     * @param newValue the new value
     */
    public final void set(V newValue) {
        value = newValue;
    }

    /**
     * Eventually sets to the given value.
     *
     * @param newValue the new value
     * @since 1.6
     */
    public final void lazySet(V newValue) {
        unsafe.putOrderedObject(this, valueOffset, newValue);
    }

    /**
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     * @param expect the expected value
     * @param update the new value
     * @return {@code true} if successful. False return indicates that
     * the actual value was not equal to the expected value.
     */
    public final boolean compareAndSet(V expect, V update) {
        return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
    }

    /**
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     *
     * <p><a href="package-summary.html#weakCompareAndSet">May fail
     * spuriously and does not provide ordering guarantees</a>, so is
     * only rarely an appropriate alternative to {@code compareAndSet}.
     *
     * @param expect the expected value
     * @param update the new value
     * @return {@code true} if successful
     */
    public final boolean weakCompareAndSet(V expect, V update) {
        return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
    }

    /**
     * Atomically sets to the given value and returns the old value.
     *
     * @param newValue the new value
     * @return the previous value
     */
    @SuppressWarnings("unchecked")
    public final V getAndSet(V newValue) {
        return (V)unsafe.getAndSetObject(this, valueOffset, newValue);
    }

    /**
     * Atomically updates the current value with the results of
     * applying the given function, returning the previous value. The
     * function should be side-effect-free, since it may be re-applied
     * when attempted updates fail due to contention among threads.
     *
     * @param updateFunction a side-effect-free function
     * @return the previous value
     * @since 1.8
     */
    public final V getAndUpdate(UnaryOperator<V> updateFunction) {
        V prev, next;
        do {
            prev = get();
            next = updateFunction.apply(prev);
        } while (!compareAndSet(prev, next));
        return prev;
    }

    /**
     * Atomically updates the current value with the results of
     * applying the given function, returning the updated value. The
     * function should be side-effect-free, since it may be re-applied
     * when attempted updates fail due to contention among threads.
     *
     * @param updateFunction a side-effect-free function
     * @return the updated value
     * @since 1.8
     */
    public final V updateAndGet(UnaryOperator<V> updateFunction) {
        V prev, next;
        do {
            prev = get();
            next = updateFunction.apply(prev);
        } while (!compareAndSet(prev, next));
        return next;
    }

Example:多线程同时修改对象


public class ThreeReference {

public static void main(String[] args) {
AtomicReference<User> atomicReference = new AtomicReference<>();
User user = new User("uu", "beijing");
for (int i = 0; i < 5; ++i){
ReferenceThread thread = new ReferenceThread(atomicReference,user,i);
thread.start();
try {
thread.join();
} catch (InterruptedException ex){
ex.printStackTrace();
}
}
System.out.println(atomicReference.get());
}
}

class ReferenceThread extends Thread{

AtomicReference<User> atomicReference;

User user;

int i;

public ReferenceThread(AtomicReference<User> atomicReference, User user,int i){
this.atomicReference = atomicReference;
this.user = user;
this.i = i;
}

@Override
public void run(){
user.setAddress("beijing" + i);
user.setUsername("uu" + i);
atomicReference.set(user);
//System.out.println(user.toString());
}
}

class User{

private String username;

private String address;

public User(String username,String address){
this.username = username;
this.address = address;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getAddress() {
return address;
}

public void setAddress(String address) {
this.address = address;
}

@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", address='" + address + '\'' +
'}';
}
}

 

原子更新对象属性

如果需要更新对象的某个字段,并在多线程的情况下,能够保证线程安全,atomic同样也提供了相应的原子操作类:

  1. AtomicIntegeFieldUpdater:原子更新整型字段类;
  2. AtomicLongFieldUpdater:原子更新长整型字段类;
  3. AtomicReferenceFieldUpdater:原子更新引用类型,这种更新方式会带有版本号,为了解决CAS的ABA问题;

要想使用原子更新字段需要两步操作

  1. 原子更新字段类都是抽象类,只能通过静态方法newUpdater来创建一个更新器,并且需要设置想要更新的类和属性;
  2. 更新类的属性必须使用public volatile进行修饰;

 

posted @ 2019-05-30 10:58  江湖凶险  阅读(180)  评论(0)    收藏  举报