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