天天看點

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));
    }
}