文章目錄
JUC中的原子類都是都是依靠volatile、CAS、Unsafe類配合來實現的.
JUC中原子類介紹
atomic 翻譯成中文是原子的意思。在化學上,我們知道原子是構成一般物質的最小單位,在化學反應中是不可分割的。在我們這裏 atomic 是指一個操作是不可中斷的。即使是在多個線程一起執行的時候,一個操作一旦開始,就不會被其他線程干擾,所以,所謂原子類說簡單點就是具有原子操作特徵的類,原子操作類提供了一些修改數據的方法,這些方法都是原子操作的,在多線程情況下可以確保被修改數據的正確性。
JUC中原子類思維導圖
基本類型原子類
使用原子的方式更新基本類型
- AtomicInteger:int類型原子類
- AtomicLong:long類型原子類
- AtomicBoolean :boolean類型原子類
AtomicInteger 類常用方法
public final int get() //獲取當前的值
public final int getAndSet(int newValue)//獲取當前的值,並設置新的值
public final int getAndIncrement()//獲取當前的值,並自增
public final int getAndDecrement() //獲取當前的值,並自減
public final int getAndAdd(int delta) //獲取當前的值,並加上預期的值
boolean compareAndSet(int expect, int update) //如果輸入的數值等於預期值,則以原子方式將該值設置爲輸入值(update)
public final void lazySet(int newValue)//最終設置爲newValue,使用 lazySet 設置之後可能導致其他線程在之後的一小段時間內還是可以讀到舊的值。
部分源碼
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
2個關鍵字段說明:
value:使用volatile修飾,可以確保value在多線程中的可見性。
valueOffset:value屬性在AtomicInteger中的偏移量,通過這個偏移量可以快速定位到value字段,這個是實現AtomicInteger的關鍵。
getAndIncrement源碼:
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
內部調用的是Unsafe類中的getAndAddInt方法,我們看一下getAndAddInt源碼:
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
說明:
this.getIntVolatile:可以確保從主內存中獲取變量最新的值。
compareAndSwapInt:CAS操作,CAS的原理是拿期望的值和原本的值作比較,如果相同則更新成新的值,可以確保在多線程情況下只有一個線程會操作成功,不成功的返回false
。
上面有個do-while循環,compareAndSwapInt返回false之後,會再次從主內存中獲取變量的值,繼續做CAS操作,直到成功爲止。
getAndAddInt操作相當於線程安全的count++操作,如同:
synchronize(lock){
count++;
}
count++操作實際上是被拆分爲3步驟執行:
獲取count的值,記做A:A=count
將A的值+1,得到B:B = A+1
讓B賦值給count:count = B
多線程情況下會出現線程安全的問題,導致數據不準確。
synchronize的方式會導致佔時無法獲取鎖的線程處於阻塞狀態,性能比較低。CAS的性能比synchronize要快很多。
數組類型原子類
使用原子的方式更新數組裏的某個元素,可以確保修改數組中數據的線程安全性
- AtomicIntegerArray:整形數組原子操作類
- AtomicLongArray:長整形數組原子操作類
- AtomicReferenceArray :引用類型數組原子操作類
AtomicIntegerArray 類常用方法
public final int get(int i) //獲取 index=i 位置元素的值
public final int getAndSet(int i, int newValue)//返回 index=i 位置的當前的值,並將其設置爲新值:newValue
public final int getAndIncrement(int i)//獲取 index=i 位置元素的值,並讓該位置的元素自增
public final int getAndDecrement(int i) //獲取 index=i 位置元素的值,並讓該位置的元素自減
public final int getAndAdd(int delta) //獲取 index=i 位置元素的值,並加上預期的值
boolean compareAndSet(int expect, int update) //如果輸入的數值等於預期值,則以原子方式將 index=i 位置的元素值設置爲輸入值(update)
public final void lazySet(int i, int newValue)//最終 將index=i 位置的元素設置爲newValue,使用 lazySet 設置之後可能導致其他線程在之後的一小段時間內還是可以讀到舊的值。
引用類型原子類
基本類型原子類只能更新一個變量,如果需要原子更新多個變量,需要使用 引用類型原子類。
- AtomicReference:引用類型原子類
- AtomicStampedRerence:原子更新引用類型裏的字段原子類
- AtomicMarkableReference :原子更新帶有標記位的引用類型
AtomicReference 和 AtomicStampedRerence
AtomicReference 和 AtomicInteger 非常類似,不同之處在於 AtomicInteger是對整數的封裝,而AtomicReference則是對應普通的對象引用,它可以確保你在修改對象引用時的線程安全性
AtomicStampedRerence的幾個Api在AtomicReference的基礎上新增了有關時間戳的信息。
//比較設置,參數依次爲:期望值、寫入新值、期望時間戳、新時間戳
public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp);
//獲得當前對象引用
public V getReference();
//獲得當前時間戳
public int getStamp();
//設置當前對象引用和時間戳
public void set(V newReference, int newStamp);
對象的屬性修改原子類
如果需要原子更新某個類裏的某個字段時,需要用到對象的屬性修改原子類。
- AtomicIntegerFieldUpdater:原子更新整形字段的值
- AtomicLongFieldUpdater:原子更新長整形字段的值
- AtomicReferenceFieldUpdater :原子更新引用類型字段的值
要想原子地更新對象的屬性需要兩步:
第一步,因爲對象的屬性修改類型原子類都是抽象類,所以每次使用都必須使用靜態方法 newUpdater()創建一個更新器
,並且需要設置想要更新的類和屬性。
第二步,更新的對象屬性必須使用 public volatile 修飾符。
AtomicReferenceFieldUpdater
調用AtomicReferenceFieldUpdater靜態方法newUpdater創建AtomicReferenceFieldUpdater對象
public static <U, W> AtomicReferenceFieldUpdater<U, W>
newUpdater(Class<U> tclass, Class<W> vclass, String fieldName)
參數
tclass:需要操作的字段所在的類
vclass:操作字段的類型
fieldName:字段名稱