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