Atomic+源码分析

184 阅读3分钟

概述

jdk1.5后提供了一些原子操作的类,应用开发者使用起来,更加简单、高效、更加安全的进行并发更新变量。这些原子类都在java.util.concurrent.atomic包下。总体来说,有五类原子类。

  • 原子更新基本类型类:AtomicInteger、AtomicBoolean、AtomicLong、
  • 原子更新数组:AtoicIntegerArray、AtomicLongArray、AtomicReferenceArray
  • 原子更新引用:AtomicReference、AtomicMarkableReference、AtomicStampedReference
  • 原子更新字段类:AtomicLongFieldUpdater
  • Java8新增类:LongAdder、DoubleAdder等(这些也属于原子更新基本类型,不过Java8对高并发高竞争场景中的操作,在这些类中做了一些优化,使之更高效,单独拿了出来)

使用例子更多测试例子在github中

public class AtomicIntegerTest {
    private static int normalInteger = 0;
    private static AtomicInteger atomicInteger = new AtomicInteger(0);
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new AddInteger());
        Thread t2 = new Thread(new AddInteger());
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("普通Integer对象多线程++操作结果:" + normalInteger);
        System.out.println("AtomicInteger对象多线程累加操作结果:" + atomicInteger.intValue());
    }

    static class AddInteger implements Runnable {
        @Override
        public void run() {
            //每个线程执行一万次累加操作
            for(int i=0; i<10000; i++) {
                normalInteger++;
                atomicInteger.getAndIncrement();
            }
        }
    }
}

运行结果:

普通Integer对象多线程++操作结果:16176
AtomicInteger对象多线程累加操作结果:20000

结果可以看到,普通的Integer在多线程中共享,i++操作会使结果错误。而AtomicInteger运算正常。

AtomicInteger源码

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

    // 赋值unsafe对象
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    //value字段相对于对象的偏移量
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
    //使用volatile修饰,保证内存可见性和指令重排
    private volatile int value;
    
    //由于value是volatile的,直接返回可以保证是最新值
    public final int get() {
        return value;
    }
    
    //AtomicInteger最常用的一个方法,返回当前值并将当前值进行加1
    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }
    ...
}

AtomicInteger使用了volatile保证内存的可见性和unsafe的CAS操作。 上面的注释已经比较清楚了,我们主要看下getAndIncrement方法,该方法调用了unsafe.getAndAddInt方法

public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        //使用Volatile语义的获取当前Int值
        var5 = this.getIntVolatile(var1, var2);
        //更新成功则返回,否则自旋进行下一次取值,更新
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}

由于自旋CAS操作有两个缺点。

  • ABA问题:即由于多线程共享数据原因,某个数据被其他线程从A更新到B,又从B更新到A,如果程序在意这个变化过程,那么可以使用版本号解决,每次更新进行版本号加1操作。AtomicStampedReference就提供了这个功能。
  • CAS自旋消耗CPU资源:Jdk1.8增加了LongAdder等类优化,当竞争非常激烈时,LongAdder等类性能明显好于AtomicLong。

AtomicStampedReference源码

public class AtomicStampedReference<V> {

    //将目标对应的引用和版本号stamp构造成一个Pair对象
    private static class Pair<T> {
        final T reference;
        final int stamp;
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
        static <T> Pair<T> of(T reference, int stamp) {
            return new Pair<T>(reference, stamp);
        }
    }
    
    //使用volatile 修饰pair对象
    private volatile Pair<V> pair;

    //AtomicStampedReference构造函数
    public AtomicStampedReference(V initialRef, int initialStamp) {
        pair = Pair.of(initialRef, initialStamp);
    }
    
    public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) {
        Pair<V> current = pair;
        //expectedReference和pair.reference相等 && expectedStamp和pair.stamp相等时CAS操作
        //需要注意的newReference和newStamp也与pair进行了比较,即如果期望值和版本号都没变,直接返回更新成功,不再进行CAS替换
        return
            expectedReference == current.reference &&
            expectedStamp == current.stamp &&
            ((newReference == current.reference &&
              newStamp == current.stamp) ||
             casPair(current, Pair.of(newReference, newStamp)));
    }
    。。。
}