Unsafe:
Java無法直接訪問底層操作系統,而是通過本地(native)方法來訪問。不過儘管如此,JVM還是開了一個後門,
JDK中有一個類Unsafe,它提供了硬件級別的原子操作。
CAS
CAS,Compare and Swap即比較並交換,設計併發算法時常用到的一種技術,java.util.concurrent包全完建立在CAS之上,沒有CAS也就沒有此包,可見CAS的重要性。
當前的處理器基本都支持CAS,只不過不同的廠家的實現不一樣罷了。CAS有三個操作數:內存值V、舊的預期值A、要修改的值B,當且僅當預期值A和內存值V相同時,將內存值修改爲B並返回true,否則什麼都不做並返回false。
CAS也是通過Unsafe實現的
由CAS分析AtomicInteger原理
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;
關於這段代碼中出現的幾個成員屬性:
1、Unsafe是CAS的核心類,前面已經講過了
2、valueOffset表示的是變量值在內存中的偏移地址,因爲Unsafe就是根據內存偏移地址獲取數據的原值的
3、value是用volatile修飾的,這是非常關鍵的
下面找一個方法getAndIncrement來研究一下AtomicInteger是如何實現的,比如我們常用的addAndGet方法:
public final int addAndGet(int delta) { for (;;) { int current = get(); int next = current + delta; if (compareAndSet(current, next)) return next; } }
public final int get() { return value; }
這段代碼如何在不加鎖的情況下通過CAS實現線程安全,我們不妨考慮一下方法的執行:
1、AtomicInteger裏面的value原始值爲3,即主內存中AtomicInteger的value爲3,根據Java內存模型,線程1和線程2各自持有一份value的副本,值爲3
2、線程1運行到第三行獲取到當前的value爲3,線程切換
3、線程2開始運行,獲取到value爲3,利用CAS對比內存中的值也爲3,比較成功,修改內存,此時內存中的value改變比方說是4,線程切換
4、線程1恢復運行,利用CAS比較發現自己的value爲3,內存中的value爲4,得到一個重要的結論-->此時value正在被另外一個線程修改,所以我不能去修改它
5、線程1的compareAndSet失敗,循環判斷,因爲value是volatile修飾的,所以它具備可見性的特性,線程2對於value的改變能被線程1看到,只要線程1發現當前獲取的value是4,內存中的value也是4,說明線程2對於value的修改已經完畢並且線程1可以嘗試去修改它
6、最後說一點,比如說此時線程3也準備修改value了,沒關係,因爲比較-交換是一個原子操作不可被打斷,線程3修改了value,線程1進行compareAndSet的時候必然返回的false,這樣線程1會繼續循環去獲取最新的value並進行compareAndSet,直至獲取的value和內存中的value一致爲止
整個過程中,利用CAS機制保證了對於value的修改的線程安全性。
CAS的缺點
CAS看起來很美,但這種操作顯然無法涵蓋併發下的所有場景,並且CAS從語義上來說也不是完美的,存在這樣一個邏輯漏洞:如果一個變量V初次讀取的時候是A值,並且在準備賦值的時候檢查到它仍然是A值,那我們就能說明它的值沒有被其他線程修改過了嗎?如果在這段期間它的值曾經被改成了B,然後又改回A,那CAS操作就會誤認爲它從來沒有被修改過。這個漏洞稱爲CAS操作的"ABA"問題。java.util.concurrent包爲了解決這個問題,提供了一個帶有標記的原子引用類"AtomicStampedReference",它可以通過控制變量值的版本來保證CAS的正確性。不過目前來說這個類比較"雞肋",大部分情況下ABA問題並不會影響程序併發的正確性,如果需要解決ABA問題,使用傳統的互斥同步可能迴避原子類更加高效。