Java并发学习(七)-AtomicInteger基本数据类型类

来源:互联网 发布:转行金融 知乎 编辑:程序博客网 时间:2024/06/05 18:43

从Java5开始,出现了concurrent并发包,这里主要先介绍atomic包下面的AtomicXXX诸如AtomicBoolean,AtomicInteger,AtomicLong等原子更新类,它们内部实现思想基本一致,这里以AtomicInteger为例进行介绍。
AtomicXXX主要包括:AtomicIntegerAtomicBooleanAtomicLongAtomicReference

What is AtomicInteger

Java有8种数据类型,并且每个数据类型都有一个包装类,如int和Integer,它们之间的转化也就是我们常称作的自动拆箱和装箱的过程。但是呢,它们只是一个简单的数据,当在并发编程下,没有任何特殊的语义。
记得以前分析过volatile,这里有需要可以看:Java并发学习(二)-JMM。
volatile能保证可见性,以及阻止编译器和处理器对其重排序,并且对单一数据读写具有原子性,然而对于复合操作却没有原子性,比如i++。
那么如果需要一种原子性int呢?
那就是atomic包下面的AtomicInteger了。

AtomicInteger的实现

那么AtomicXXX具体怎么实现的呢?
volatile的基本数据类型+CAS操作。
volatile保证可见性,当一个线程修改volatile变量时,其他线程拿到的都是修改后的最新值。关于CAS操作可以阅读:Java并发学习(六)-深入分析CAS操作 。
里面的方法都是对Unsafe方法的封装,而Unsafe里面的方法都是JNI方法,通过调用底层c++方法从而实现指令级的原子操作。
看看里面一些基本方法:

    ...    //set方法    public final void set(int newValue) {          value = newValue;      }    //lazyset,没有storeload屏障的set    public final void lazySet(int newValue) {        unsafe.putOrderedInt(this, valueOffset, newValue);    }    //获取并且设置    public final int getAndSet(int newValue) {        return unsafe.getAndSetInt(this, valueOffset, newValue);    }    //原子更新值    public final boolean compareAndSet(int expect, int update) {        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);    }    //weak的CAS,也就是没有volatile语义的CAS,没有加入内存屏障    public final boolean weakCompareAndSet(int expect, int update) {        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);    }    //自增加,返回原先值    public final int getAndIncrement() {        return unsafe.getAndAddInt(this, valueOffset, 1);    }    //自减少,返回原先值    public final int getAndDecrement() {        return unsafe.getAndAddInt(this, valueOffset, -1);    }    //原子性增加delta值    public final int getAndAdd(int delta) {        return unsafe.getAndAddInt(this, valueOffset, delta);    }    //自增1,返回最终值    public final int incrementAndGet() {        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;    }    //阻塞式更新,并且对prev进行一个IntUnaryOperator操作运算    public final int updateAndGet(IntUnaryOperator updateFunction) {        int prev, next;        do {            prev = get();            next = updateFunction.applyAsInt(prev);        } while (!compareAndSet(prev, next));        return next;    }    //阻塞式更新,并对prev和x,进行二元运算操作。    public final int getAndAccumulate(int x,IntBinaryOperator accumulatorFunction) {        int prev, next;        do {            prev = get();            next = accumulatorFunction.applyAsInt(prev, x);        } while (!compareAndSet(prev, next));        return prev;    }    ...

上面代码,从理解层面来讲,还是不难的,这里主要看lazySetweakCompareAndSet 两个方法的意义。

lazySet

从上面代码可以看到,由于value是volatile类型,所以普通方法set,就是写入volatile类型变量。此时JVM会插入特定的内存屏障,内存语义具有可见性。

而lazySet呢?看意思是“懒惰”的set,什么意思呢?
set是内存语义是立即对其他线程可见,则lazySet则是不一定立即可见。
为什么会这样呢?

  1. 首先set()是对volatile变量的一个写操作, 我们知道volatile的write为了保证对其他线程的可见性会追加以下两个Fence(内存屏障)
    1)StoreStore // 在intel cpu中, 不存在[写写]重排序, 这个可以直接 省略了
    2)StoreLoad // 这个是所有内存屏障里最耗性能的

  2. lazySet()省去了StoreLoad屏障, 只留下StoreStore 。详见Doug Lea 的描述:http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6275329

所以这样一来,在效率上毫无疑问lazySet要比set高很多,可以这样理解,lazySet在intel cpu中,其实就可以看做普通变量的写操作了

lazySet比set()具有性能优势,但是使用场景很有限。

weakCompareAndSet

基于Java8分析,在上述代码中,可以很明显的看到weakCompareAndSet方法和compareAndSet方法,具有相同的实现,但是为啥名字不同呢?
其实按照Doug Lea本来的设计意图,是想吧weakCompareAndSet设定为一种在性能上更加高效的方法。
由于compareAndSet和其他读取并更新的操作,拥有相同的内存语义,即具有原子性。
所以设计了weakCompareAndSet方法,在读上具有通用的原子性,但是写方面不具有volatile语义了,换而言之,weakCompareAndSet的写操作,不能即时被其他线程可见。
上述详情参见:http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4996165

但是话又说回来,JDK它们两个的层面,它们两个的实现是一样的,也就是说jdk并没有真正实现它,只是保留了这个观点,现在可能没改变,但将来可能会按照设想的实现。

IntUnaryOperator和IntBinaryOperator

在上面代码最下面,可以看到两个具有阻塞性的方法,updateAndGetgetAndAccumulate ,这两个方法是Java8新加入的,增加了函数式编程的运用。
IntUnaryOperator

@FunctionalInterfacepublic interface IntUnaryOperator {    /**     * 一个操作数的函数     */    int applyAsInt(int operand);    //compose    default IntUnaryOperator compose(IntUnaryOperator before) {        Objects.requireNonNull(before);        return (int v) -> applyAsInt(before.applyAsInt(v));    }    //andThen    default IntUnaryOperator andThen(IntUnaryOperator after) {        Objects.requireNonNull(after);        return (int t) -> after.applyAsInt(applyAsInt(t));    }    //返回当前运算值    static IntUnaryOperator identity() {        return t -> t;    }}

如上,IntUnaryOperator 就是一个队单个int型数的操作运算,而compose和andThen,可以参看:Java8的Function接口学习(compose和andThen) 。

IntBinaryOperator:
IntBinaryOperator则更加简单,也是函数式的方法接口,就只有一个待实现方法:

@FunctionalInterfacepublic interface IntBinaryOperator {    /**     * 两个int的原酸     *     * @param left the first operand     * @param right the second operand     * @return the operator result     */    int applyAsInt(int left, int right);}

AtomicLong注意之处

有些机器是32位的,所以会把64位long类型volatile拆分成2个32位进行计算,但有些不是的。所以在实现AtomicLong的时候,如果是32位,那就需要加上锁来实现CAS操作。JVM特别的还加了一个判断,来区分是32位:

    /**     * Records whether the underlying JVM supports lockless     * compareAndSwap for longs. While the Unsafe.compareAndSwapLong     * method works in either case, some constructions should be     * handled at Java level to avoid locking user-visible locks.     */    static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8();    /**     * Returns whether underlying JVM supports lockless CompareAndSet     * for longs. Called only once and cached in VM_SUPPORTS_LONG_CAS.     */    private static native boolean VMSupportsCS8();

AtomicBoolean注意之处

在AtomicBoolean中,作为volatile变量的,并不是boolean类型,而是一个int类型的0和1来分别表示false和true。

    private volatile int value;    /**     * Creates a new {@code AtomicBoolean} with the given initial value.     *     * @param initialValue the initial value     */    public AtomicBoolean(boolean initialValue) {        value = initialValue ? 1 : 0;    }

参考资料:
1. jdk文档
2. http://ifeve.com/juc-atomic-class-lazyset-que/
3. http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6275329
4. http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4996165

原创粉丝点击