Java中提供了很多原子操作類來保證共享變量操作的原子性。這些原子操作的底層原理都是使用了CAS機制。在使用一門技術(shù)之前,了解這個技術(shù)的底層原理是非常重要的,所以本篇文章就先來講講什么是CAS機制,CAS機制存在的一些問題以及在Java中怎么使用CAS機制。
十載的沙河口網(wǎng)站建設(shè)經(jīng)驗,針對設(shè)計、前端、開發(fā)、售后、文案、推廣等六對一服務(wù),響應(yīng)快,48小時及時工作處理。營銷型網(wǎng)站的優(yōu)勢是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動調(diào)整沙河口建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計,從而大程度地提升瀏覽體驗。創(chuàng)新互聯(lián)從事“沙河口網(wǎng)站設(shè)計”,“沙河口網(wǎng)站推廣”以來,每個客戶項目都認真落實執(zhí)行。
其實Java并發(fā)框架的基石一共有兩塊,一塊是本文介紹的CAS,另一塊就是AQS,后續(xù)也會寫文章介紹。
CAS機制是一種數(shù)據(jù)更新的方式。在具體講什么是CAS機制之前,我們先來聊下在多線程環(huán)境下,對共享變量進行數(shù)據(jù)更新的兩種模式:悲觀鎖模式和樂觀鎖模式。
悲觀鎖更新的方式認為:在更新數(shù)據(jù)的時候大概率會有其他線程去爭奪共享資源,所以悲觀鎖的做法是:第一個獲取資源的線程會將資源鎖定起來,其他沒爭奪到資源的線程只能進入阻塞隊列,等第一個獲取資源的線程釋放鎖之后,這些線程才能有機會重新爭奪資源。synchronized就是java中悲觀鎖的典型實現(xiàn),synchronized使用起來非常簡單方便,但是會使沒爭搶到資源的線程進入阻塞狀態(tài),線程在阻塞狀態(tài)和Runnable狀態(tài)之間切換效率較低(比較慢)。比如你的更新操作其實是非??斓?,這種情況下你還用synchronized將其他線程都鎖住了,線程從Blocked狀態(tài)切換回Runnable華的時間可能比你的更新操作的時間還要長。
樂觀鎖更新方式認為:在更新數(shù)據(jù)的時候其他線程爭搶這個共享變量的概率非常小,所以更新數(shù)據(jù)的時候不會對共享數(shù)據(jù)加鎖。但是在正式更新數(shù)據(jù)之前會檢查數(shù)據(jù)是否被其他線程改變過,如果未被其他線程改變過就將共享變量更新成最新值,如果發(fā)現(xiàn)共享變量已經(jīng)被其他線程更新過了,就重試,直到成功為止。CAS機制就是樂觀鎖的典型實現(xiàn)。
CAS,是Compare and Swap的簡稱,在這個機制中有三個核心的參數(shù):
如上圖中,主存中保存V值,線程中要使用V值要先從主存中讀取V值到線程的工作內(nèi)存A中,然后計算后變成B值,最后再把B值寫回到內(nèi)存V值中。多個線程共用V值都是如此操作。CAS的核心是在將B值寫入到V之前要比較A值和V值是否相同,如果不相同證明此時V值已經(jīng)被其他線程改變,重新將V值賦給A,并重新計算得到B,如果相同,則將B值賦給V。
值得注意的是CAS機制中的這步步驟是原子性的(從指令層面提供的原子操作),所以CAS機制可以解決多線程并發(fā)編程對共享變量讀寫的原子性問題。
1. ABA問題
ABA問題:CAS在操作的時候會檢查變量的值是否被更改過,如果沒有則更新值,但是帶來一個問題,最開始的值是A,接著變成B,最后又變成了A。經(jīng)過檢查這個值確實沒有修改過,因為最后的值還是A,但是實際上這個值確實已經(jīng)被修改過了。為了解決這個問題,在每次進行操作的時候加上一個版本號,每次操作的就是兩個值,一個版本號和某個值,A——>B——>A問題就變成了1A——>2B——>3A。在jdk中提供了AtomicStampedReference類解決ABA問題,用Pair這個內(nèi)部類實現(xiàn),包含兩個屬性,分別代表版本號和引用,在compareAndSet中先對當前引用進行檢查,再對版本號標志進行檢查,只有全部相等才更新值。
2. 可能會消耗較高的CPU
看起來CAS比鎖的效率高,從阻塞機制變成了非阻塞機制,減少了線程之間等待的時間。每個方法不能絕對的比另一個好,在線程之間競爭程度大的時候,如果使用CAS,每次都有很多的線程在競爭,也就是說CAS機制不能更新成功。這種情況下CAS機制會一直重試,這樣就會比較耗費CPU。因此可以看出,如果線程之間競爭程度小,使用CAS是一個很好的選擇;但是如果競爭很大,使用鎖可能是個更好的選擇。在并發(fā)量非常高的環(huán)境中,如果仍然想通過原子類來更新的話,可以使用AtomicLong的替代類:LongAdder。
3. 不能保證代碼塊的原子性
Java中的CAS機制只能保證共享變量操作的原子性,而不能保證代碼塊的原子性。
從Java5開始引入了對CAS機制的底層的支持,在這之前需要開發(fā)人員編寫相關(guān)的代碼才可以實現(xiàn)CAS。在原子變量類Atomic中(例如AtomicInteger、AtomicLong)可以看到CAS操作的代碼,在這里的代碼都是調(diào)用了底層(核心代碼調(diào)用native修飾的方法)的實現(xiàn)方法。在AtomicInteger源碼中可以看getAndSet方法和compareAndSet方法之間的關(guān)系,compareAndSet方法調(diào)用了底層的實現(xiàn),該方法可以實現(xiàn)與一個volatile變量的讀取和寫入相同的效果。在前面說到了volatile不支持例如i++這樣的復(fù)合操作,在Atomic中提供了實現(xiàn)該操作的方法。JVM對CAS的支持通過這些原子類(Atomic***)暴露出來,供我們使用。
而Atomic系類的類底層調(diào)用的是Unsafe類的API,Unsafe類提供了一系列的compareAndSwap*方法,下面就簡單介紹下Unsafe類的API:
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
下面是JDK8新增的函數(shù),這里只列出Long類型操作。
long getAndSetLong(Object obj, long offset, long update)方法:獲取對象obj中偏移量為offset的變量volatile語義的當前值,并設(shè)置變量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;
}