天天看点

JUC之原子类JUC原子类

JUC原子类

文章目录

  • JUC原子类
    • 一、分类
    • 二、基本类型
      • 1、AtomicInteger
      • 2、AtomicLong
      • 3、AtomicBoolean
    • 三、数组类型
      • 1、AtomicIntegerArray
    • 四、引用类型
      • 1、AtomicReference
      • 2、AtomicReferenceArray
    • 五、对象属性类型
      • 1、AtomicIntegerFieldUpdater(抽象类)
    • demo

一、分类

Juc原子类可以分为以下四大类:

  • 基本类型
    • AtomicInteger, AtomicLong, AtomicBoolean
  • 数组类型
    • AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray
  • 引用类型
    • AtomicReference, AtomicStampedRerence, AtomicMarkableReference
  • 对象属性类型
    • AtomicIntegerFieldUpdater, AtomicLongFieldUpdater, AtomicReferenceFieldUpdater

二、基本类型

1、AtomicInteger

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

    /*
     * This class intended to be implemented using VarHandles, but there
     * are unresolved cyclic startup dependencies.
     */
    private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
    private static final long VALUE;

    static {
        try {
            //valueOffset,也就是内存偏移量。偏移量在AtomicInteger中很重要,AtomicInteger的原子操作都靠内存偏移量来实现的。
            VALUE = U.objectFieldOffset
                    //获取value变量的offset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (ReflectiveOperationException e) {
            throw new Error(e);
        }
    }

    /**
     * 使用了volatile,保证多线程的可见性。但是会禁止重排序等优化,因此会下降性能。
     * 非高并发不要使用。
     */
    private volatile int value;
    }
           

​ 对于使用了volatile,其实就是为了处理多线程对于共享变量的可见性,保证数据的及时性。但是,我们也知道,做一个加法的过程中,实际上是一个load->update->writeback,这里我们保证了load是相同,但是update以及后面的回写就会出现线程安全问题,因此,volatile只能用于保证第一步的load是安全的,及时的。

​ 那么,接下来我们就得想办法处理,update这个过程,让它也是安全的,这样就能够实现原子操作。

  • 加锁(悲观)

    很明显,这个能够通过加锁解决,但是,锁的性能消耗太多了,以至于哪怕没有线程竞争,也会消耗性能,果断放弃。

  • CAS(乐观)

    先假定没有冲突直接进行操作,如果因为有冲突而失败就重试,直到操作成功。其中乐观锁用到的机制就是CAS,Compare and Swap。

    具体的操作就是:

    • 假定没有冲突,就是直接修改,这个时候如果没有冲突,那么很明显,性能提上来了。
    • 如果发生冲突,那么进行重试,判断expect与内存中的value是否相同,如果相同,执行修改。
    看一下源码的实现:
public final int getAndIncrement() {
  //VALUE就是value的offset.
        return U.getAndAddInt(this, VALUE, 1);
    }
@HotSpotIntrinsicCandidate
public final int getAndAddInt(Object o, long offset, int delta) {
     int v;
    do {
      //获取当前的值
       v = getIntVolatile(o, offset);
      //如果交换失败,则进行重试。
    } while (!weakCompareAndSetInt(o, offset, v, v + delta));
     return v;
 }
//该方法参数为offset, v:前者是内存偏移量,v是当前值;如果两者相等,就加1。
//如果不等,证明内存值被改变,重新获取当前值,再进行比较。
weakCompareAndSetInt(o, offset, v, v + delta);
           

总结:

  • volatile保证可见性
  • 每次修改之前获取一下当前值。
  • 实际操作的时候比较一下一样不一样,不一样就重试(重新获取当前值),一样就修改。

2、AtomicLong

​ 在学习了AtomicInteger之后,姑且猜测Long也是跟Integer相同的实现原理,当然,可能会有些许不同。直接上源码吧。

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

    /**
     * Records whether the underlying JVM supports lockless
     * compareAndSet for longs. While the intrinsic compareAndSetLong
     * 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();

    /*
     * This class intended to be implemented using VarHandles, but there
     * are unresolved cyclic startup dependencies.
     */
    private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
    private static final long VALUE;

    static {
        try {
            VALUE = U.objectFieldOffset
                (AtomicLong.class.getDeclaredField("value"));
        } catch (ReflectiveOperationException e) {
            throw new Error(e);
        }
    }

    private volatile long value;
    }
           

​ 既然起始的部分都是通过volatile与内存偏移量来处理,那么,实现的底层也是CAS,如下:

public final long getAndIncrement() {
        return U.getAndAddLong(this, VALUE, 1L);
    }
  @HotSpotIntrinsicCandidate
    public final long getAndAddLong(Object o, long offset, long delta) {
        long v;
        do {
            v = getLongVolatile(o, offset);
        } while (!weakCompareAndSetLong(o, offset, v, v + delta));
        return v;
    }
           

3、AtomicBoolean

​ 原子布尔类型,本质上也是通过CAS实现,不过,我们还是通过源码看看有什么不同。

public class AtomicBoolean implements java.io.Serializable {
    private static final long serialVersionUID = 4654671469794556979L;
    private static final VarHandle VALUE;
    static {
        try {
            MethodHandles.Lookup l = MethodHandles.lookup();
            VALUE = l.findVarHandle(AtomicBoolean.class, "value", int.class);
        } catch (ReflectiveOperationException e) {
            throw new Error(e);
        }
    }

    private volatile int value;
   }
           

​ 令我有点意外的是,原子布尔类的实现居然是使用0,1作为底层实现,而非boolean。

public final boolean compareAndSet(boolean expectedValue, boolean newValue) {
        return VALUE.compareAndSet(this,
                                   (expectedValue ? 1 : 0),
                                   (newValue ? 1 : 0));
    }
           

可以看到,我们传入的boolean类型的参数会被转化成为int类型。

三、数组类型

​ 读完上面的原子类实现之后,其实可以发现,dougLea在设计这些原子类的时候,都是采用了CAS的思想,但是对于不同的原子类,它会有不同的细节实现,以下是原子数组,这里只列举原子整型数组,对于其它数组,有兴趣的可以自己去阅读源码。

1、AtomicIntegerArray

public class AtomicIntegerArray implements java.io.Serializable {
    private static final long serialVersionUID = 2862133569453604235L;
    private static final VarHandle AA
        = MethodHandles.arrayElementVarHandle(int[].class);
   //使用final,而不使用volatile,原因是volatile也无法保证元素的可见性。
    //而final可以保证元素是经过初始化,且具有地址的不变性。
  //对于数组的元素,是在JNI层次使用volatile.
    private final int[] array;

    /**
     * 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();
    }
}
           

​ 其实我很惊讶,居然不是

​ 而是

​ 通过查找资料,原来,private volatile int[] array并不能够使得数组中的元素具有内存可见性,它只能使得array这个数组变量是具有可见性的。

​ 于是,探究为什么是采用final?

  • final的内存语义:
    • final指向的对象是不能变更地址的。
    • final对象使用时,必须是经过初始化的。
    虽然了解了final语义,但是还是有点不明白,那就读源码吧。
public final void set(int i, int newValue) {
        AA.setVolatile(array, i, newValue);
    }
           

​ 懂了,对于原子数组类型的实现,底层还是volatile加cas,虽然数组没有标明是volatile(使用volatile标识数组也没有丝毫作用),而是采用了final,确保使用时,对象都是经过初始化的。

​ 在JNI实现时,还是使用了setVolatile,也就是底层还是调用volatile语义去实现。再配合CAS,实现指定的数组元素的原子性操作。

四、引用类型

1、AtomicReference

​ 以上已经学了数组、单一类型两种,那么对于引用类型来说,也不会难理解。

public class AtomicReference<V> implements java.io.Serializable {
    private static final long serialVersionUID = -1848883965231344442L;
    private static final VarHandle VALUE;
    static {
        try {
            MethodHandles.Lookup l = MethodHandles.lookup();
            VALUE = l.findVarHandle(AtomicReference.class, "value", Object.class);
        } catch (ReflectiveOperationException e) {
            throw new Error(e);
        }
    }

    private volatile V value;
}
           

​ 可以很明显的看出,引用类型无非就是采用volatile修饰对应的对象。并且在替换、改变的时候使用CAS作为实现。

2、AtomicReferenceArray

​ 参考AtomicIntegerArray,可以知道,原子引用数组也是采用final修饰数组变量,volatile加CAS作为底层实现。

五、对象属性类型

​ 在学习了以上的原子类之后,我们会发现,它们真的很好用,但是,有时候我们的代码已经写好了,只是想在某项业务中将原本的非原子性的属性改为具有原子性,怎么搞?dougLea已经想好,并且设计出了针对某些类的非原子属性进行原子性操作的,就是AtomicXXFieldUpdater。

1、AtomicIntegerFieldUpdater(抽象类)

AtomicIntegerFieldUpdaterImpl(final Class<T> tclass,
                                      final String fieldName,
                                      final Class<?> caller) {
            final Field field;
            final int modifiers;
            try {
                field = AccessController.doPrivileged(
                    new PrivilegedExceptionAction<Field>() {
                        public Field run() throws NoSuchFieldException {
                            return tclass.getDeclaredField(fieldName);
                        }
                    });
                modifiers = field.getModifiers();
                sun.reflect.misc.ReflectUtil.ensureMemberAccess(
                    caller, tclass, null, modifiers);
                ClassLoader cl = tclass.getClassLoader();
                ClassLoader ccl = caller.getClassLoader();
                if ((ccl != null) && (ccl != cl) &&
                    ((cl == null) || !isAncestor(cl, ccl))) {
                    sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass);
                }
            } catch (PrivilegedActionException pae) {
                throw new RuntimeException(pae.getException());
            } catch (Exception ex) {
                throw new RuntimeException(ex);
            }

            if (field.getType() != int.class)
                throw new IllegalArgumentException("Must be integer type");

            if (!Modifier.isVolatile(modifiers))
                throw new IllegalArgumentException("Must be volatile type");

            // Access to protected field members is restricted to receivers only
            // of the accessing class, or one of its subclasses, and the
            // accessing class must in turn be a subclass (or package sibling)
            // of the protected member's defining class.
            // If the updater refers to a protected field of a declaring class
            // outside the current package, the receiver argument will be
            // narrowed to the type of the accessing class.
            this.cclass = (Modifier.isProtected(modifiers) &&
                           tclass.isAssignableFrom(caller) &&
                           !isSamePackage(tclass, caller))
                          ? caller : tclass;
            this.tclass = tclass;
            this.offset = U.objectFieldOffset(field);
        }
           

​ 实现:

  • 通过反射,拿到对应的字段进行封装,并且调用封装后的一些操作方法。
  • 这一系列的操作方法的底层都是CAS。

注意:对应的字段必须是volatile,这点我想无需多说了。

demo

public class AtomicField {
  //内存可见性,必须保证。
    private volatile int value;

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    /**
     * 不是原子操作,不安全。
     */
    public void unSafeIncre() {
        value += 1;
    }

    public static void main(String[] args) {
        AtomicField atomicField = new AtomicField();
        //通过构造updater
        AtomicIntegerFieldUpdater<AtomicField> updater = AtomicIntegerFieldUpdater.newUpdater(AtomicField.class, "value");
        updater.addAndGet(atomicField, 1000);
        System.out.println(updater.get(atomicField));
    }
}