AQS加鎖機制Synchronized相似點詳解
正文
在并發(fā)多線程的情況下,為了保證數(shù)據(jù)安全性,一般我們會對數(shù)據(jù)進行加鎖,通常使用Synchronized或者ReentrantLock同步鎖。Synchronized是基于JVM實現(xiàn),而ReentrantLock是基于Java代碼層面實現(xiàn)的,底層是繼承的AQS。
AQS全稱 AbstractQueuedSynchronizer
,即抽象隊列同步器,是一種用來構建鎖和同步器的框架。
我們常見的并發(fā)鎖ReentrantLock、CountDownLatch、Semaphore、CyclicBarrier都是基于AQS實現(xiàn)的,所以說不懂AQS實現(xiàn)原理的,就不能說了解Java鎖。
當我仔細研究AQS底層加鎖原理,發(fā)現(xiàn)竟然跟Synchronized加鎖原理有驚人的相似。讓我突然想到一句名言,記不清怎么說了,意思是框架底層原理很相似,大家多學習底層原理。
Synchronized的加鎖流程在前幾篇文章已經(jīng)詳細講過,沒看過一塊再溫習一下。
1. Synchronized加鎖流程
我們先想一下Synchronized的加鎖需求,如果讓你設計Synchronized的對象鎖存儲結構,該怎么設計?
- 多個線程執(zhí)行到Synchronized代碼塊,只有一個線程獲取鎖,然后執(zhí)行同步代碼塊(需要記錄哪個線程獲取了對象鎖)。
- 其他線程被阻塞(被阻塞的線程,是不是可以用鏈表設計個阻塞隊列?)
- 持有鎖的線程調(diào)用wait方法,釋放鎖,等待被喚醒(等待的線程,是不是可以用鏈表設計個等待隊列?)。
- 被阻塞的線程開始競爭鎖
- 調(diào)用notify方法,喚醒等待的線程,被喚醒的線程進入阻塞隊列,一塊競爭鎖。
上面描述了Synchronized的加鎖流程,Synchronized的對象鎖存儲結構是不是跟咱們想的一樣?實際就是的。
下面是對象鎖的存儲數(shù)據(jù)結構(由C++實現(xiàn)):
ObjectMonitor() { _header = NULL; _count = 0; _waiters = 0, _recursions = 0; _object = NULL; _owner = NULL; // 持有鎖的線程 _WaitSet = NULL; // 等待隊列,存儲處于wait狀態(tài)的線程 _WaitSetLock = 0 ; _Responsible = NULL ; _succ = NULL ; _cxq = NULL ; FreeNext = NULL ; _EntryList = NULL ; // 阻塞隊列,存儲處于等待鎖block狀態(tài)的線程 _SpinFreq = 0 ; _SpinClock = 0 ; OwnerIsThread = 0 ; }
上圖展示了對象鎖的基本工作機制:
- 當多個線程同時訪問一段同步代碼時,首先會進入 _EntryList隊列中阻塞。
- 當某個線程獲取到對象的對象鎖后進入臨界區(qū)域,并把對象鎖中的 _owner變量設置為當前線程,即獲得對象鎖。
- 若持有對象鎖的線程調(diào)用 wait() 方法,將釋放當前持有的對象鎖,_owner變量恢復為null,同時該線程進入 _WaitSet 集合中等待被喚醒。
- 在_WaitSet集合中的線程被喚醒,會被再次放到_EntryList隊列中,重新競爭獲取鎖。
- 若當前線程執(zhí)行完畢也將釋放對象鎖并復位變量的值,以便其他線程進入獲取鎖。
Synchronized對象鎖存儲結構和加鎖流程,竟然跟咱們想的一樣。
再看一下ReentrantLock的存儲結構和加鎖流程,有沒有相似的地方。
2. AQS加鎖原理
先分析一下,我們使用AQS的加鎖需求:
- 多個線程執(zhí)行到ReentrantLock.lock方法的時候,只有一個線程獲取鎖,然后執(zhí)行同步代碼塊(需要記錄哪個線程獲取了對象鎖)。
- 其他線程被阻塞(被阻塞的線程,是不是可以用鏈表設計個阻塞隊列?名叫”同步隊列“?)
- 持有鎖的線程調(diào)用await方法,釋放鎖,等待被喚醒(等待的線程,是不是可以用鏈表設計個等待隊列?名叫”條件隊列“?)。
- 被阻塞的線程開始競爭鎖
- 調(diào)用signal方法,喚醒等待的線程,被喚醒的線程進入阻塞隊列,一塊競爭鎖。
AQS的需求跟Synchronized一模一樣。
我們再看一下AQS實際的加鎖機制是怎么設計的?是不是跟Synchronized相似?
AQS的加鎖流程并不復雜,只要理解了同步隊列和條件隊列,以及它們之間的數(shù)據(jù)流轉(zhuǎn),就算徹底理解了AQS。
- 當多個線程競爭AQS鎖時,如果有個線程獲取到鎖,就把ower線程設置為自己
- 沒有競爭到鎖的線程,在同步隊列中阻塞(同步隊列采用雙向連接,尾插法)。
- 持有鎖的線程調(diào)用await方法,釋放鎖,追加到條件隊列的末尾(條件隊列采用單鏈條,尾插法)。
- 持有鎖的線程調(diào)用signal方法,喚醒條件隊列的頭節(jié)點,并轉(zhuǎn)移到同步隊列的末尾。
- 同步隊列的頭節(jié)點優(yōu)先獲取到鎖
可以看到AQS和Synchronized的加鎖流程幾乎是一模一樣的,AQS中同步隊列就是Synchronized中EntryList,AQS中條件隊列就是Synchronized中的waitSet,兩個隊列之間的數(shù)據(jù)轉(zhuǎn)移流程也是一樣的。
3. 總結
AQS跟Synchronized的加鎖流程是一樣的,都是通過同步隊列和條件隊列實現(xiàn)的,阻塞狀態(tài)的線程被放到同步隊列中,等待狀態(tài)的線程被放到條件隊列中,從條件隊列喚醒的線程又被轉(zhuǎn)移到同步隊列末尾,一塊競爭鎖。
看完AQS加鎖流程,還沒有人不懂AQS的?
下篇文章再講一下AQS加鎖具體的源碼實現(xiàn)。里面有很多精巧的設計,值得我們學習。
比如:
為什么同步隊列要設計成雙向鏈表?而條件隊列要設計成單鏈表?
為什么AQS加鎖性能這么好(樂觀鎖CAS使用)?
同步隊列和條件隊列中節(jié)點怎么用一個對象實現(xiàn)?
釋放鎖后,怎么喚醒同步隊列中線程?
以上就是AQS加鎖機制Synchronized相似點詳解的詳細內(nèi)容,更多關于AQS加鎖機制Synchronized的資料請關注腳本之家其它相關文章!
相關文章
Springboot整合多數(shù)據(jù)源配置流程詳細講解
這篇文章主要介紹了Springboot整合多數(shù)據(jù)源配置流程,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧2023-03-03SpringCloud gateway request的body驗證或修改方式
這篇文章主要介紹了SpringCloud gateway request的body驗證或修改方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07Java的Hibernate框架中Criteria查詢使用的實例講解
這篇文章主要介紹了Java的Hibernate框架中Criteria查詢使用的實例講解,Hibernate是Java的SSH三大web開發(fā)框架之一,需要的朋友可以參考下2016-01-01