您好,登錄后才能下訂單哦!
Java中提供了很多原子操作類來保證共享變量操作的原子性。這些原子操作的底層原理都是使用了CAS機制。在使用一門技術之前,了解這個技術的底層原理是非常重要的,所以本篇文章就先來講講什么是CAS機制,CAS機制存在的一些問題以及在Java中怎么使用CAS機制。
其實Java并發框架的基石一共有兩塊,一塊是本文介紹的CAS,另一塊就是AQS,后續也會寫文章介紹。
CAS機制是一種數據更新的方式。在具體講什么是CAS機制之前,我們先來聊下在多線程環境下,對共享變量進行數據更新的兩種模式:悲觀鎖模式和樂觀鎖模式。
悲觀鎖更新的方式認為:在更新數據的時候大概率會有其他線程去爭奪共享資源,所以悲觀鎖的做法是:第一個獲取資源的線程會將資源鎖定起來,其他沒爭奪到資源的線程只能進入阻塞隊列,等第一個獲取資源的線程釋放鎖之后,這些線程才能有機會重新爭奪資源。synchronized就是java中悲觀鎖的典型實現,synchronized使用起來非常簡單方便,但是會使沒爭搶到資源的線程進入阻塞狀態,線程在阻塞狀態和Runnable狀態之間切換效率較低(比較慢)。比如你的更新操作其實是非常快的,這種情況下你還用synchronized將其他線程都鎖住了,線程從Blocked狀態切換回Runnable華的時間可能比你的更新操作的時間還要長。
樂觀鎖更新方式認為:在更新數據的時候其他線程爭搶這個共享變量的概率非常小,所以更新數據的時候不會對共享數據加鎖。但是在正式更新數據之前會檢查數據是否被其他線程改變過,如果未被其他線程改變過就將共享變量更新成最新值,如果發現共享變量已經被其他線程更新過了,就重試,直到成功為止。CAS機制就是樂觀鎖的典型實現。
CAS,是Compare and Swap的簡稱,在這個機制中有三個核心的參數:
如上圖中,主存中保存V值,線程中要使用V值要先從主存中讀取V值到線程的工作內存A中,然后計算后變成B值,最后再把B值寫回到內存V值中。多個線程共用V值都是如此操作。CAS的核心是在將B值寫入到V之前要比較A值和V值是否相同,如果不相同證明此時V值已經被其他線程改變,重新將V值賦給A,并重新計算得到B,如果相同,則將B值賦給V。
值得注意的是CAS機制中的這步步驟是原子性的(從指令層面提供的原子操作),所以CAS機制可以解決多線程并發編程對共享變量讀寫的原子性問題。
1. ABA問題
ABA問題:CAS在操作的時候會檢查變量的值是否被更改過,如果沒有則更新值,但是帶來一個問題,最開始的值是A,接著變成B,最后又變成了A。經過檢查這個值確實沒有修改過,因為最后的值還是A,但是實際上這個值確實已經被修改過了。為了解決這個問題,在每次進行操作的時候加上一個版本號,每次操作的就是兩個值,一個版本號和某個值,A——>B——>A問題就變成了1A——>2B——>3A。在jdk中提供了AtomicStampedReference類解決ABA問題,用Pair這個內部類實現,包含兩個屬性,分別代表版本號和引用,在compareAndSet中先對當前引用進行檢查,再對版本號標志進行檢查,只有全部相等才更新值。
2. 可能會消耗較高的CPU
看起來CAS比鎖的效率高,從阻塞機制變成了非阻塞機制,減少了線程之間等待的時間。每個方法不能絕對的比另一個好,在線程之間競爭程度大的時候,如果使用CAS,每次都有很多的線程在競爭,也就是說CAS機制不能更新成功。這種情況下CAS機制會一直重試,這樣就會比較耗費CPU。因此可以看出,如果線程之間競爭程度小,使用CAS是一個很好的選擇;但是如果競爭很大,使用鎖可能是個更好的選擇。在并發量非常高的環境中,如果仍然想通過原子類來更新的話,可以使用AtomicLong的替代類:LongAdder。
3. 不能保證代碼塊的原子性
Java中的CAS機制只能保證共享變量操作的原子性,而不能保證代碼塊的原子性。
從Java5開始引入了對CAS機制的底層的支持,在這之前需要開發人員編寫相關的代碼才可以實現CAS。在原子變量類Atomic中(例如AtomicInteger、AtomicLong)可以看到CAS操作的代碼,在這里的代碼都是調用了底層(核心代碼調用native修飾的方法)的實現方法。在AtomicInteger源碼中可以看getAndSet方法和compareAndSet方法之間的關系,compareAndSet方法調用了底層的實現,該方法可以實現與一個volatile變量的讀取和寫入相同的效果。在前面說到了volatile不支持例如i++這樣的復合操作,在Atomic中提供了實現該操作的方法。JVM對CAS的支持通過這些原子類(Atomic***)暴露出來,供我們使用。
而Atomic系類的類底層調用的是Unsafe類的API,Unsafe類提供了一系列的compareAndSwap*方法,下面就簡單介紹下Unsafe類的API:
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
下面是JDK8新增的函數,這里只列出Long類型操作。
long getAndSetLong(Object obj, long offset, long update)方法:獲取對象obj中偏移量為offset的變量volatile語義的當前值,并設置變量volatile語義的值為update。
//這個方法只是封裝了compareAndSwapLong的使用,不需要自己寫重試機制
public final long getAndSetLong(Object var1, long var2, long var4) {
long var6;
do {
var6 = this.getLongVolatile(var1, var2);
} while(!this.compareAndSwapLong(var1, var2, var6, var4));
return var6;
}
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。