概念
- compareAndSwap翻譯過來就是 比較並交換
- cas底層 調用的是unSafe,unSafed對底層的修改調用的native方法(CPU併發原語),天然原子性
代碼說話
- 創建一個AtomicInteger類,初始化值5,此時線程A去修改,把5讀到工作內存,修改成2000,在寫回主內存時,會比較當時拿到工作內存的5和現在主內存是否一致, 一致則修改成2000,不一致則表示被其他線程修改了。
- 底層代碼
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;
/**
* Atomically increments by one the current value.
*
* @return the previous value
*/
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
// 這裏會做自旋
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;
}
-
假設線程A和線程B同時執行getAndAddInt操作
-
1 AtomicInteger裏面的value原始值爲3,即主內存中Atomicinteger的value=3,根據Jmm模型,線程A和線程B各自持有一份值爲3的value的副本拷貝到各自的工作內存。
-
2 線程A通過getIntVolatile(var1, var2)拿到value值3,這時線程A被掛起。
-
3 線程B通過getIntVolatile(var1,var2)拿到value值3,此時線程B得到CPU執行權,執行compareAndSwapint方法比較內存值也爲3,成功修改成4 ,此時主內存值爲4了
-
4 線程A恢復,執行compareSwapint方法比較,發現自己手裏的值3和主內存不一致了,本次修改失敗,重新讀取再來一遍
-
5 線程A重新獲取value值,因爲變量value被volatile修飾,是可見的,compareSwapint方法成功,修改成功。
-
UnSafe類的理解
- UnSafe
-
它是CAS的核心類,由於java方法無法直接訪問底層系統,需要通過本地方法來訪問,Unsage相當於是一個後門,基於該類可以直接操作特定內存的數據。
-
Unsafe類存在於sun.misc包中,其 內部方法操作可以像C的指針一樣直接操作內存
-
Unsafe類中的所有方法都是native修飾的,也就是說Unsafe類中的方法都直接調用操作系統底層資源執行相應的任務
-
- compareAndSwapInt
-
該方法的實現位於unsafe.cpp中
-
使用cmpxchg指令比較並更新值(保證原子性),下面是底層寫法
// obj 是當前工作內存的值 // offset 是獲取主內存的值 oop p = JNIHandles::resolve(obj) jint* addr = (jint *) index_oop_from_field_offset_long(p, offset) (Atomic::cmpxchg(x, addr, e))
-
AtomicInteger是如何保證線程安全
-
變量valueOffset 標識該變量值在內存中的 偏移地址,因爲Unsafe就是根據內存 偏移地址 來獲取數據的
public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); } 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; }
-
value用volatile修飾,保證了內存之間的可見性
private volatile int value;
-
總結
- 利用CAS(底層是靠硬件層面的鎖) + volatile來保證原子性,從而避免synchronized的高開銷,提升執行效率
cas的缺點
- 當前線程如果每次比較並交換的時候都返回false,就會一直請求,會給CPU帶來很大的開銷
- ABA問題
- 當線程A去讀主內存的值爲1, 另一個線程B修改了這個值爲2並寫回主內存,然後又重新修改回1寫回主內存,然後線程A進行修改寫回主內存是可以成功的
- 儘管cas成功了,但是不代表這個過程是沒問題的
- 解決:
- 通過添加版本號的方式
- 如:AtomicReference會有ABA問題、AtomicStampedReference沒有ABA問題
底層深究
- CPU併發原語爲什麼就能保證原子性 (借鑑)
-
CPU 處理器速度遠遠大於在主內存中的,爲了解決速度差異,在他們之間架設了多級緩存,如 L1、L2、L3 級別的緩存,這些緩存離CPU越近就越快,將頻繁操作的數據緩存到這裏,加快訪問速度
-
現在都是多核 CPU 處理器,每個 CPU 處理器內維護了一塊字節的內存,每個內核內部維護着一塊字節的緩存,當多線程併發讀寫時,就會出現緩存數據不一致的情況
-
總線鎖定
-
當一個處理器要操作共享變量時,在 BUS 總線上發出一個 Lock 信號,其他處理就無法操作這個共享變量了。
-
缺點很明顯,總線鎖定在阻塞其它處理器獲取該共享變量的操作請求時,也可能會導致大量阻塞,從而增加系統的性能開銷。
-
-
緩存鎖定
-
後來的處理器都提供了緩存鎖定機制,也就說當某個處理器對緩存中的共享變量進行了操作,其他處理器會有個嗅探機制,將其他處理器的該共享變量的緩存失效,待其他線程讀取時會重新從主內存中讀取最新的數據,基於 MESI 緩存一致性協議來實現的。
-
MESI是Modified、Exclusive、Shared、Invalid這四個單詞的首字母。這4個字母分別代表4種狀態
狀態 描述 監聽任務 Modified 該緩存行有效,但是該緩存數據已經被當前核心修改,此時和DRAM中數據不一致。我們將其置爲M,其他的核中緩存行都會置爲I。 監聽總線上所有對該緩存行寫回DRAM的操作(不希望別人寫入),需要將該操作延遲到自己將緩存行寫回到主存後變成S狀態。 Excluside(互斥) 該緩存行有效,數據和RAM的數據一致,數據只存在當前內核工作內存中,只有他在使用是獨佔的。 監聽總線上所有從DRAM讀取該緩存行的操作,一旦有讀的,需要將狀態置爲S狀態。 Shared(共享) 該緩存行有效,不過當前緩存行在多個核中都有,並且大家以及DRAM中的都一樣。 監聽其他的緩存中將該緩存置爲I或者爲E的事件,將狀態置爲I狀態。 Invalid(無效) 表明該緩存行無效,如果想要獲取數據的話,就去DRAM中加載最新的。 不需要監聽。
-
-
現代的處理器基本都支持和使用的緩存鎖定機制
-
-