帶你快速搞定java多線程(4)
1、AQS 是什么?
AQS 是類 AbstractQueuedSynchronizer的簡稱,也是常用鎖的基類,比如常見的ReentrantLock,Semaphore,CountDownLatch 等等。
AQS提供了一種實(shí)現(xiàn)阻塞鎖和一系列依賴FIFO等待隊(duì)列的同步器的框架。是Java提供的一種模板,一般在現(xiàn)有同步器無法完成的時(shí)候可以自行擴(kuò)展。當(dāng)然也可以自己實(shí)現(xiàn),不過既然有現(xiàn)成的為什么還要自己瞎雞兒寫。
2、AQS 模型
注意:圖來自網(wǎng)上,具體的原地點(diǎn)在哪我也不知道。如果有問題可以聯(lián)系我。
解釋:整個(gè)模型相當(dāng)于在食堂吃飯,只開了一個(gè)窗口,只有一個(gè)打飯的師傅(state),所有想要吃飯的線程必須排隊(duì)等待,直到輪到自己。
AQS就是基于隊(duì)列,用共享變量state,線程通過CAS去改變狀態(tài)符,成功則獲取鎖成功,失敗則進(jìn)入等待隊(duì)列,等待被喚醒。代碼解決現(xiàn)實(shí)問題,現(xiàn)實(shí)問題催生解決方案。
3、AQS state
state 代表 共享資源和一個(gè)FIFO線程等待隊(duì)列(多線程爭用資源被阻塞時(shí)會(huì)進(jìn)入此隊(duì)列)。
state的訪問方式有三種:
- getState()
- setState()
- compareAndSetState()
4、AQS 兩種資源共享方式:
1.Exclusive:獨(dú)占,只有一個(gè)線程能執(zhí)行,如ReentrantLock
2.Share:共享,多個(gè)線程可以同時(shí)執(zhí)行,如Semaphore、CountDownLatch、ReadWriteLock,CyclicBarrier。
5、模板方式實(shí)現(xiàn)自定義
不同的自定義同步器爭用共享資源的方式也不同。自定義同步器在實(shí)現(xiàn)時(shí)只需要實(shí)現(xiàn)共享資源state的獲取與釋放方式即可,至于具體線程等待隊(duì)列的維護(hù)(如獲取資源失敗入隊(duì)/喚醒出隊(duì)等),AQS已經(jīng)在頂層實(shí)現(xiàn)好了。自定義時(shí)主要實(shí)現(xiàn)以下幾種方法:
isHeldExclusively()
:該線程是否正在獨(dú)占資源。只有用到condition才需要去實(shí)現(xiàn)它。tryAcquire(int)
:獨(dú)占方式。嘗試獲取資源,成功則返回true,失敗則返回false。tryRelease(int)
:獨(dú)占方式。嘗試釋放資源,成功則返回true,失敗則返回false。tryAcquireShared(int)
:共享方式。嘗試獲取資源。負(fù)數(shù)表示失??;0表示成功,但沒有剩余可用資源;正數(shù)表示成功,且有剩余資源。tryReleaseShared(int)
:共享方式。嘗試釋放資源,如果釋放后允許喚醒后續(xù)等待結(jié)點(diǎn)返回true,否則返回false。
以ReentrantLock為例,state初始化為0,表示未鎖定狀態(tài)。A線程lock()時(shí),會(huì)調(diào)用tryAcquire()獨(dú)占該鎖并將state+1。此后,其他線程再tryAcquire()時(shí)就會(huì)失敗,直到A線程unlock()到state=0(即釋放鎖)為止,其它線程才有機(jī)會(huì)獲取該鎖。當(dāng)然,釋放鎖之前,A線程自己是可以重復(fù)獲取此鎖的(state會(huì)累加),這就是可重入的概念。但要注意,獲取多少次就要釋放多么次,這樣才能保證state是能回到零態(tài)的。
再以CountDownLatch以例,任務(wù)分為N個(gè)子線程去執(zhí)行,state也初始化為N(注意N要與線程個(gè)數(shù)一致)。這N個(gè)子線程是并行執(zhí)行的,每個(gè)子線程執(zhí)行完后countDown()一次,state會(huì)CAS減1。等到所有子線程都執(zhí)行完后(即state=0),會(huì)unpark()主調(diào)用線程,然后主調(diào)用線程就會(huì)從await()函數(shù)返回,繼續(xù)后余動(dòng)作。
一般來說,自定義同步器要么是獨(dú)占方法,要么是共享方式,他們也只需實(shí)現(xiàn)tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一種即可。但AQS也支持自定義同步器同時(shí)實(shí)現(xiàn)獨(dú)占和共享兩種方式,如ReentrantReadWriteLock。
6、鎖的分類:公平鎖和非公平鎖,樂觀鎖和悲觀鎖
- 公平鎖:當(dāng)線程A獲取訪問該對象,獲取到鎖后,此時(shí)內(nèi)部存在一個(gè)計(jì)數(shù)器num+1,其他線程想訪問該對象,就會(huì)進(jìn)行排隊(duì)等待(等待隊(duì)列最前一個(gè)線程處于待喚醒狀態(tài)),直到線程A釋放鎖(num = 0),此時(shí)會(huì)喚醒處于待喚醒狀態(tài)的線程進(jìn)行獲取鎖的操作,一直循環(huán)。如果線程A再次嘗試獲取該對象鎖時(shí),會(huì)檢查該對象鎖釋放已經(jīng)被占用,如果還是當(dāng)前線程占用鎖,則直接獲得鎖,不用進(jìn)入排隊(duì)。
- 非公平鎖:當(dāng)線程A在釋放鎖后,等待對象的線程會(huì)進(jìn)行資源競爭,競爭成功的線程將獲取該鎖,其他線程繼續(xù)睡眠。
公平鎖是嚴(yán)格的以FIFO的方式進(jìn)行鎖的競爭,但是非公平鎖是無序的鎖競爭,剛釋放鎖的線程很大程度上能比較快的獲取到鎖,隊(duì)列中的線程只能等待,所以非公平鎖可能會(huì)有“饑餓”的問題。但是重復(fù)的鎖獲取能減小線程之間的切換,而公平鎖則是嚴(yán)格的線程切換,這樣對操作系統(tǒng)的影響是比較大的,所以非公平鎖的吞吐量是大于公平鎖的,這也是為什么JDK將非公平鎖作為默認(rèn)的實(shí)現(xiàn)。
- 悲觀鎖:總是假設(shè)最壞的情況,每次想要使用數(shù)據(jù)的時(shí)候就恰好別人也要修改數(shù)據(jù),一切是以安全第一,所以在每次操作資源的時(shí)候都會(huì)先加鎖,不管有沒有人搶,然后獨(dú)占資源。Java中synchronized和ReentrantLock等獨(dú)占鎖就是悲觀鎖思想的實(shí)現(xiàn)
- 樂觀鎖:樂觀鎖和悲觀鎖剛好相反,自己使用資源的時(shí)候沒有人搶,所以不需要上鎖。樂觀鎖的實(shí)現(xiàn)方案一般來說有兩種:版本號機(jī)制 和 CAS實(shí)現(xiàn) 。樂觀鎖多適用于多度的應(yīng)用類型,這樣可以提高吞吐量。
7、CAS
CAS(Compare And Swap),即比較并交換。是解決多線程并行情況下使用鎖造成性能損耗的一種機(jī)制,CAS操作包含三個(gè)操作數(shù)——內(nèi)存位置(V)、預(yù)期原值(A)和新值(B)。如果內(nèi)存位置的值與預(yù)期原值相匹配,那么處理器會(huì)自動(dòng)將該位置值更新為新值。否則,處理器不做任何操作。無論哪種情況,它都會(huì)在CAS指令之前返回該位置的值。CAS有效地說明了“我認(rèn)為位置V應(yīng)該包含值A(chǔ);如果包含該值,則將B放到這個(gè)位置;否則,不要更改該位置,只告訴我這個(gè)位置現(xiàn)在的值即可。sun.misc.Unsafe 類提供了硬件級別的原子操作來實(shí)現(xiàn)這個(gè)CAS。java.util.concurrent 包下的大量類都使用了這個(gè) Unsafe.java 類的CAS操作。Unsafe 包是C++的接口實(shí)現(xiàn),直接可以訪問計(jì)算機(jī)的內(nèi)存等敏感操作,所以標(biāo)記為Unsafe。
CAS 是直接調(diào)用計(jì)算機(jī)的硬件指令,是硬件級別的線程同步,也是一種樂觀鎖。
8、總結(jié)
從整體上說了下AQS,沒有深入到代碼層級,先理解思想,后看具體實(shí)現(xiàn)。你理解了嗎?下期具體分析一個(gè)CountDownLatch 源碼,從細(xì)節(jié)理解AQS。
本篇文章就到這里了,希望能對你有所幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
springboot攔截器過濾token,并返回結(jié)果及異常處理操作
這篇文章主要介紹了springboot攔截器過濾token,并返回結(jié)果及異常處理操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09詳解使用SSM實(shí)現(xiàn)簡單工作流系統(tǒng)之實(shí)現(xiàn)篇
這篇文章主要介紹了使用SSM實(shí)現(xiàn)簡單工作流系統(tǒng)之實(shí)現(xiàn)篇,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-12-12SpringMVC空指針異常NullPointerException解決及原理解析
這篇文章主要介紹了SpringMVC空指針異常NullPointerException解決及原理解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08idea快速搭建spring cloud注冊中心與注冊的方法
這篇文章主要介紹了idea快速搭建spring cloud注冊中心與注冊的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-07-07解讀Java和JavaScript區(qū)別與聯(lián)系
這篇文章主要介紹了解讀Java和JavaScript區(qū)別與聯(lián)系,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02Java實(shí)現(xiàn)HTML轉(zhuǎn)為Word的示例代碼
本文以Java代碼為例為大家詳細(xì)介紹如何實(shí)現(xiàn)將HTML文件轉(zhuǎn)為Word文檔(.docx、.doc)。在實(shí)際開發(fā)場景中可參考此方法來轉(zhuǎn)換,感興趣的可以了解一下2022-06-06Spring Boot整合Spring Data Jpa代碼實(shí)例
這篇文章主要介紹了Spring Boot整合Spring Data Jpa代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11