丨原子類無鎖工具的典范_第1頁
丨原子類無鎖工具的典范_第2頁
丨原子類無鎖工具的典范_第3頁
丨原子類無鎖工具的典范_第4頁
丨原子類無鎖工具的典范_第5頁
已閱讀5頁,還剩19頁未讀 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認(rèn)領(lǐng)

文檔簡介

其實對于簡單的原子性問題,還有一種無鎖方案。JaaDK并發(fā)包將這種無鎖方案封裝提煉之后,實現(xiàn)了一系列的原子類。不過,在深入介紹原子類的實現(xiàn)之前,我們先看看如何利用原子類解決累加器問題,這樣你會對原子類有個初步的認(rèn)識。在下面的代碼中,原來的long型變量count替換為了原子類AtomicLong,原來的count+=1替換成了count.getAndIncrement(),僅需要這兩處簡單的改動就能使add10K()方法變成線程安全的,原子類的使用還是挺簡單的。publicclassTestAtomicLongcountnewvoidadd10K()intidx=while(idx++<10000) 10無鎖方案相對互斥鎖方案,最大的好處就是性能?;コ怄i方案為了保證互斥性,需要執(zhí)行加鎖、操作,而加鎖、操作本身就消耗性能;同時拿不到鎖的線程還會進入阻塞狀態(tài),進而觸發(fā)線程切換,線程切換對性能的消耗也很大。相比之下,無鎖方案則完全沒有加鎖、的性能消耗,同時還能保證互斥性,既解決了問題,又沒有帶來新的問題,可謂絕佳方案。那它是如何做到的呢?其實原子類性能高的很簡單,硬件支持而已。CPU為了解決并發(fā)問題,提供了CAS指令(CAS,全稱是CompareAndSwap,即“比較并交換”)。CAS指令包含3個參數(shù):共享變量的內(nèi)存地址A、用于比較的值B和共享變量的新值C;并且只有當(dāng)內(nèi)存中地址A處的值等于B時,才能將內(nèi)存中地址A處的值更新為新值C。作為一條CPU指令,CAS指令本身是能夠保證原子性的。你可以通過下面CAS指令的模擬代碼來理解CAS的工作原理。在下面的模擬程序中有個參數(shù),一個是期望值expect,另一個是需要寫入的新值newValue,只有當(dāng)目前的值和期望值expect相等時,才會將count更新為newValueclass2int3synchronizedint4intexpect,int5//讀目前count6intcurValue=7//比較目前count值是否==期望8if(curValue==9//如果是,則更新countcount=}//返回寫入前的return}}你仔細(xì)地再次思考一下這句話,“只有當(dāng)count值和期望值expect等時,才會將count更新為newValue。”要怎么理解這句話呢?對于前面提到的累加器的例子,count+=1的一個問題是:基于內(nèi)存中cout前值A(chǔ)計算出來的cont+=1為A+1,在將A+1寫入內(nèi)存的時候,很可能此時內(nèi)存中ount已經(jīng)被其他線程更新過了,這樣就會導(dǎo)致錯誤地覆蓋其他線程寫入的值(如果你覺得理解起來還有,建議你再重新看看《01|可見性、原子性和有序性問題:并發(fā)編程Bug的》)。也就是說,只有當(dāng)內(nèi)存中cont的值等于期望值A(chǔ)時,才能將內(nèi)存中ount的值更新為計算結(jié)果A+1,這不就是CAS的語義嗎!使用CAS來解決并發(fā)問題,一般都會伴隨著自旋,而所謂自旋,其實就是循環(huán)嘗試如,實現(xiàn)一個線程安全的count+=1操作,“CAS+自旋”的實現(xiàn)方案如下所示,首先計算newValue=count+1,如果cas(count,newValue)返回的值不等于count,則意味著線程在執(zhí)行完代碼①處之后,執(zhí)行代碼②處之前,count的值被其他線程更新過。那此時該怎么處理呢?可以采用自旋方案,就像下面代碼中展示的,可以重新讀count的值來計算newValue并嘗試再次更新,直到成功。classvolatileint345678923

count+=1donewValue=count+1;}while(countcas(count,newValue)}模擬實現(xiàn)CAS,僅用來幫助理解synchronizedintcas(intexpect,int//讀目前countintcurValue=//比較目前count值是否==期望if(curValue==//如果是,則更新countcount=}//返回寫入前的return}通過上面的示例代碼,想必你已經(jīng)發(fā)現(xiàn)了,CAS這種無鎖方案,完全沒有加鎖、操作,即便兩個線程完全同時執(zhí)行addOe方法,也不會有線程被阻塞,所以相對于互斥鎖方案來說,性能好了很多。但是在CAS案中,有一個問題可能會常被你忽略,那就是ABA的問題。什么是ABA前面我們提到“如果cas(count,newValue回的值不等于count,意味著線程在執(zhí)行完代碼①處之后,執(zhí)行代碼②處之前,count的值被其他線程更新過”,那如果cas(count,newValue回的值等于count,是否就能夠認(rèn)為count值沒有被其他線程更新過呢?顯然不是的,假設(shè)count本是A,線程T1執(zhí)行完代碼①處之后,執(zhí)行代碼②處之前,有可能count被線程T2更新成了B,之后又被T3更新回了A,這樣線程T1雖然看到的一直是A,但是其實已經(jīng)被其他線程更新過了,這就是ABA問題。可能大多數(shù)情況下我們并不關(guān)心ABA問題,例如數(shù)值的原子遞增,但也不能所有情況下都不關(guān)心,例如原子化的更新對象很可能就需要關(guān)心ABA題,因為兩個A然相等,但是第二個A的屬性可能已經(jīng)發(fā)生變化了。所以在使用CAS方案的時候,一定要先check一看Javacount在本文開始部分,我們使用原子類AtomicLong的getAndIncrement()方法替代了count+=1,從而實現(xiàn)了線程安全。原子類AtomicLong的getAndIncrement()方法內(nèi)部就是基于CAS實現(xiàn)的,下面我們來看看Java是如何使用CAS來實現(xiàn)原子化的count+=1的在Java1.8版本中,getAndIncrement()方轉(zhuǎn)調(diào)unsafe.getAndAddLong()方法。這里this和valueOffset兩個參數(shù)可以唯一確定共享變量的內(nèi)存地址。finallonggetAndIncrement()returnthis,valueOffset,4unsafe.getAndAddLong()方法的源碼如下,該方法首先會在內(nèi)存中共享變量的值,之后循環(huán)調(diào)用compareAndSwapLong法來嘗試設(shè)置共享變量的值,直到成功為止。compareAndSwapLong()是一個native方法,只有當(dāng)內(nèi)存中共享變量的值等于expected時,才會將共享變量的值更新為x,并且返回true;否則返回fasle。compareAndSwapLong的語義和CAS指令的語義的差別僅僅是返回值不同而已。publicfinallongObjecto,longoffset,longlongdo//內(nèi)存中的v=getLongVolatile(o,}whileo,offset,v,v+return10//原子性地將變量更新為//條件是內(nèi)存中的值等于//更新成功則返回nativebooleanObjecto,longlonglong另外,需要你注意的是,geAnAdLong()方法的實現(xiàn),基本上就是CAS使用的經(jīng)典范例。所以請你再次體會下面這段抽象后的代碼片段,它在很多無鎖程序中經(jīng)常出現(xiàn)。Jaa提供的原子類里面CAS一般被實現(xiàn)為comareAnet(),comareAndet的語義和CAS指令的語義的差別僅僅是返回值不同而已,omareAnSet()里面如果更新成功,則會返回re,否則返回false。do//獲取當(dāng)前oldV=//根據(jù)當(dāng)前值計算新newV=JavaDK并發(fā)包里提供的原子類內(nèi)容很豐富,我們可以將它們分為五個類別:原子化的基本數(shù)據(jù)類型、原子化的對象類型、原子化數(shù)組、原子化對象屬性更新器和原子化的累加器。這五個類別提供的方法基本上是相似的,并且每個類別都有若干原子類,你可以通過下面的原子類組成概覽圖來獲得一個全局的印象。下面我們詳細(xì)解讀這五個類別。原子類組成概覽原子化的基本相關(guān)實現(xiàn)有AtomicBoolean、AtomicIntegerAtomicLong,提供的方法主要有以下這些,詳情你可以參考SDK的源代碼,都很簡單,這里就不詳細(xì)介紹了。getAndIncrement()//原子化getAndDecrement()//原子化的iincrementAndGet()//原子化的decrementAndGet()//原子化的--//當(dāng)前值+=delta,返回+=//當(dāng)前值+=delta,返回+=//CAS操作compareAndSet(expect,//以下四個方//新值可以通過傳入func函數(shù)來計原子化的對象類相關(guān)實現(xiàn)有AtomicReference、AtomicStampedReference和AtomicReference供的方法和原子化的基本數(shù)據(jù)類型差不多,這里不再贅述。不過需要注意的是,對象的更新需要重點關(guān)注ABA問題,AtomicStampedReference和AtomicMarkableReference這兩個原子類可以解決ABA問題。解決ABA問題的思路其實很簡單,增加一個版本號維度就可以了,這個和我們在《18|tampedLock:有沒有比讀寫鎖更快的鎖?》介紹的樂觀鎖機制很類似,每次執(zhí)行CAS操作,附加再更新一個版本號,只要保證版本號是遞增的,那么即便A變成B之后再變回A,版本號也不會變回來(版本號遞增的)。AtomicSamedReferene實現(xiàn)的CAS方法就增加了版本號參數(shù),方法簽名如下:booleanVVintintAtomicMarkableReference實現(xiàn)機制則更簡單,將版本號簡化成了一個Boolean,booleanVVbooleanboolean原子化相關(guān)實現(xiàn)有AtomicIntegerArray、AtomicLongArray和AtomicReferenceArray,利用基本數(shù)據(jù)類型的區(qū)別僅僅是:每個方法多了一個數(shù)組的索引參數(shù),所以這里也不再贅述了原子化對象屬相關(guān)實現(xiàn)有AtomicIegerFieldUpaer、AtomicLonFielUpaer和AtomicRefereeFieldUpdater,利用它們可以原子化地更新對象的屬性,這三個方法都是利用反射機制實現(xiàn)的,創(chuàng)建更新器的方法如下:publicstaticnewUpdater(Class<U>String需要注意的是,對象屬性必須是volatile類型的,才能保證可見性;如果對象屬性不是volatile類型的,newUpdater()方拋出IllegalArgumentException這個運行你會發(fā)現(xiàn)newUpdater()的方法參數(shù)只有類的信息,沒有對象的,而更新對象的性,一定需要對象的,那這個參數(shù)是在哪里傳入的呢?是在原子操作的方法參數(shù)中傳入的。例如omareAnet()這個原子操作,相比原子化的基本數(shù)據(jù)類型多了一個對象oj。原子化對象屬性更新器相關(guān)的方法,相比原子化的基本數(shù)據(jù)類型僅僅是多了對象參數(shù),所以這里也不再贅述了。booleanTintint原子化DoubleAccumulator、DoubleAdder、LongAccumulatorLongAdder,這四個類僅compareAndSet()方法。如果你僅僅需要累加操作,使用原子化的累加器性能會更好。無鎖方案相對于互斥鎖方案,優(yōu)點非常多,首先性能好,其次是基本不會出現(xiàn)死鎖問題(但可能出現(xiàn)饑餓和活鎖問題,因為自旋會反復(fù)重試)。Jaa提供的原子類大部分都實現(xiàn)了omareAnet()方法,基于compareAnet()方法,你可以構(gòu)建自己的無鎖數(shù)據(jù)結(jié)Java提供的原子類能夠解決一些簡單的原子性問題,但你可能會發(fā)現(xiàn),上面我們所有原子類的方法都是針對一個共享變量的,如果你需要解決多個變量的原子性問題,建議還是使用互斥鎖方案。原子類雖好,但使用要慎之又慎。下面的示例代碼是合理庫存的原子化實現(xiàn),僅實現(xiàn)了設(shè)置庫存上限setUpper法,你覺得setUpper()方法的實現(xiàn)是否正確呢?publicclassSafeWMclassfinalintfinalintWMRange(intupper,int//省略構(gòu)造函數(shù)} finalrf=newnew //設(shè)置庫存上voidsetUpper(intWMRangeWMRangeor=//檢查if(v<thrownew nr=WMRange(v, pareAndSet(or, 26覺得這篇文章對你有幫助的話,也歡迎把它給的朋友。 不得售賣。頁面已增加防盜追蹤,將依 上一 20|并發(fā)容器:都有哪些“坑”需要我們填下一 22|Executor與線程池:如何創(chuàng)建正確的線程池精

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論