Java原子类AtomicInteger实现原理的一点总结

java原子类不多,包路径位于:java.util.concurrent.atomic,大致有如下的类:

java.util.concurrent.atomic.AtomicBoolean
java.util.concurrent.atomic.AtomicInteger
java.util.concurrent.atomic.AtomicIntegerArray
java.util.concurrent.atomic.AtomicIntegerFieldUpdater
java.util.concurrent.atomic.AtomicLong
java.util.concurrent.atomic.AtomicLongArray
java.util.concurrent.atomic.AtomicLongFieldUpdater
java.util.concurrent.atomic.AtomicMarkableReference
java.util.concurrent.atomic.AtomicReference
java.util.concurrent.atomic.AtomicReferenceArray
java.util.concurrent.atomic.AtomicReferenceFieldUpdater
java.util.concurrent.atomic.AtomicStampedReference
java.util.concurrent.atomic.DoubleAccumulator
java.util.concurrent.atomic.DoubleAdder
java.util.concurrent.atomic.LongAccumulator
java.util.concurrent.atomic.LongAdder

普通的自增减(value++或者value--)操作为非原子操作,但是借助原子类包装的自增减操作的保证了原子性。

测试代码:

package com.demo;
import java.util.concurrent.atomic.AtomicInteger;public class TestAtomicInteger {
    public static AtomicInteger value = new AtomicInteger(0);//原子类实例
//    public static int value = 0;
    public static void main(String[] args) {
        for(int i=0;i<100;i++){
            new Thread(){
                public void run() {
                    for (int j = 0; j < 100; j++) {
                        value.incrementAndGet();
//                        value++;
                    }
                };
            }.start();
        }
        while(Thread.activeCount()>1)  //保证前面的线程都执行完
            Thread.yield();
        System.out.println(value);
    }
}

这是一段经典的多线程访问共享变量的实现线程安全的例子。

如果采用注释的两处代码public static int value = 0;和value++;替换相应的代码,则会出现线程安全的问题,即使将value变量用volatile修饰保证其可见性,但是由于value++本身非原子性,仍然是线程不安全的。

重点是探索一下保证原子性操作的实现过程。

首先AtomicInteger类引入了Unsafe类,该类的路径为sun.misc.Unsafe。实际上,上面的大部分原子类都import了该类。

在AtomicInteger类内部,通过一个Unsafe类型的静态不可变的变量unsafe来引用Unsafe的实例。

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

然后,AtomicInteger类用value保存自身的数值,并用get()方法对外提供。

private volatile int value;
public AtomicInteger(int initialValue) {
    value = initialValue;
}
  ...
  ...
/**
 * Gets the current value.
 *
 * @return the current value
 */
public final int get() {
    return value;
}

有了如上前提,继续往下

AtomicInteger类的incrementAndGet()方法源码如下:

    /**
     * Atomically increments by one the current value.
     *
     * @return the updated value
     */
    public final int incrementAndGet() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return next;
        }
    }

current保存当前值,这个值在后面会作为一个是否有其他线程改变value值的依据。如果没有其他线程更改value值,那么期望上前后两个时间点获取的value值应该保持不变。next保存自增1后的值,这个值是可能被更新到value的值,如果compareAndSet(current, next)返回真,自增成功。如果返回为false,表示设置不成功,可能是其他线程更改了共享变量,导致current失效,此时再次发起一轮循环。。

compareAndSet()的源码如下:

    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

直接调用了unsafe对象的compareAndSwapInt()方法,再一次追踪该方法:

该方法位于sun.misc.Unsafe类中:

    /**
     * Atomically update Java variable to <tt>x</tt> if it is currently
     * holding <tt>expected</tt>.
     * @return <tt>true</tt> if successful
     */
    public final native boolean compareAndSwapInt(Object o, long offset,
                                                  int expected,
                                                  int x);

发现该方法是native方法。

而这些诸如compareAndSwapInt(),compareAndSwapLong(),compareAndSwapObject()等的方法由虚拟机内部对其做了特殊的处理,即时编译出来的结果就是一条平台相关的处理器CAS(Compare-and-Swap)指令,该CAS指令甚至无法通过javap解析字节码体现出来。

可以看出,原子类实现的自增操作可以保证原子性的根本原因在于硬件(处理器)的相关指令支持。将语义上需要多步操作的行为通过一条指令来完成,CAS指令可以达到这个目的。

CAS指令需要三个操作数:内存位置,旧预期值和新值。CAS要求,当且仅当当前内存位置处的值等于旧预期值时,就用新值更新当前内存位置处的值,否则它就不执行更新操作,整个过程正好映射了比较-交换(或者说比较-更新)的概念,同时处理过程是一个原子操作。

如果要进行一个参数对应,CAS指令需要的三个操作数:(内存位置,旧预期值和新值)可以分别对应compareAndSwapInt()方法的后三个参数:(long offset,int expected,int x)。此处的expected也即是current的值,x也即是next的值。

当然CAS指令的原子操作还存在一个“ABA”问题,大意即使讲,在某个线程准备进行检测时,如果其间其他线程将一个共享变量的值进行了多次更改后又回到了初始的值,此时该线程通过CAS检测会认为该共享变量未发生更改,这与实际情况不符合。

通过原子类实现线程安全是非阻塞的(对比于synchronized关键字)。其基本的思想是基于冲突检测与循环重试。具体讲就是,需要对共享数据修改时,不加锁先进行目标操作,如果发现有其他线程对同一份共享数据做修改(对应于检测到当前内存位置处的值与旧预期值不等),则放弃本次修改,重写循环再次检测并尝试修改,直到成功为止。

synchronized关键字的时间体现了悲观锁的策略思想,而原子类的实现则体现见了乐观锁的思想。

题外话:

上文提到的Unsafe类是不能被用户源程序直接加载和实例化的,因为其构造器被限定为Unsafe类私有,Unsafe只提供getUnsafe()接口间接的对外提供Unsafe的实例,但即使是这样,getUnsafe()方法对调用者要求任然颇为严苛:

    @CallerSensitive
    public static Unsafe getUnsafe() {
        Class<?> caller = Reflection.getCallerClass();
        if (!VM.isSystemDomainLoader(caller.getClassLoader()))
            throw new SecurityException("Unsafe");
        return theUnsafe;
    }

而位于sun.misc.VM类的isSystemDomainLoader(loader)方法只有在参数loader为启动加载器时,才返回true。

    /**
     * Returns true if the given class loader is in the system domain
     * in which all permissions are granted.
     */
    public static boolean isSystemDomainLoader(ClassLoader loader) {
        return loader == null;
    }

结合上述两个方法可知,通常我们用户程序的加载器为应用程序加载器,直接调用Unsafe是会抛异常的。

完结~~~

转载请注明原文地址:http://www.cnblogs.com/qcblog/p/7750388.html

参考资料:

1、深入理解Java虚拟机:JVM高级特性与最佳实践

 

posted @ 2017-10-29 17:29  Qcer  阅读(2292)  评论(0编辑  收藏  举报