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對象使用時,必須是經過初始化的。
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));
}
}