可重複鎖ReentrantLock原理分析

可重入鎖ReentrantLock實現層面依賴

一、CAS(compareAndSet)

LockSupport

基本的方法

park

park使得當前線程放棄cpu 進入等待(waiting)狀態 操作系統不會再對其進行調度

直到其他線程對它調用了unpark方法,其中unpark方法使得參數指定的線程恢復可運行狀態

[1] part和Thread.yield()區別

  • yield 只是告訴操作系統可以讓其他線程先運行,但是自己可以仍是運行態

  • park 方法則是放棄線程的運行資格,使得線程進入 WAITING 等待狀態

[2] 響應中斷

park 方法是響應中斷的,當有中斷髮生時,park方法會返回,並且重新設置線程的中斷狀態

[3]2個變體

  • parkNanos: 可以指定等待的最長時間,參數是相對於當前時間的納秒數

  • parkUntil:可以指定最長等待的時間,參數是絕對時間,相對於紀元時的毫秒數

當等待超時,方法就會返回。同時還有一些其他的變體,可以指定一個對象,表示是由於該對象而進行等待,以便於調試,一般情況下傳遞的參數爲 this

getBlocker

二、AQS

  • 提供了一個state字段 被volatile修飾 保證內存可見性、順序性

  • AQS內部維護了一個等待隊列,藉助CAS方法實現無阻塞算法進行更新

三、ReentrantLock

Sync是抽象類
NonfairSync是 fair 爲 false 時使用的類[默認]
FairSync 是 fair 爲 true 時需要使用的類

lock實現

該方法被子類重寫

如果沒有被鎖定,則使用CAS進行鎖定;如果當前線程已經被鎖定,則增加鎖定次數。

如果 tryArquire方法返回false,則acquire方法會繼續調用acquireQueued(addWaiter(Node.EXCLUSIVE), arg)。

其中,addWaiter 會新建一個節點 Node,代表當前線程,然後加入內部的等待隊列中。

在當如等待隊列之後,調用 acquireQueued 來嘗試獲取鎖,其代碼爲

是一個死循環,在每次循環中,首先檢查當前節點是否爲第一個等待的節點,
如果是且能獲取到鎖,就將當前節點從等待隊列中移除並且返回,
否則通過parkAndCheckInterrupt方法最終調用 LockSupport.park而放棄CPU,
進入等待狀態,在被喚醒之後檢查是否發生了中斷,記錄中斷標誌。並且返回中斷標誌

如果能獲得鎖則立即獲得,如若不能則加入等待隊列。被喚醒之後檢查自己是否爲第一個等待的線程,如果是且能獲得鎖則返回,否則繼續等待。如果在該過程中發生了中斷, lock 會記錄中斷標誌位,但是不會提前返回或者拋除異常

unlock實現

tryRelease 方法會修改線程狀態並且釋放鎖, unparkSuccessor 方法會調用 LockSupport.unpark 將第一個等待的線程喚醒

公平鎖和非公平鎖

公平鎖比非公平鎖在源碼實現上就多了一個檢查:當沒有其他等待時間更長的線程時,才能獲取到鎖

公平鎖模型

初始化時, state=0,表示沒有線程過來搶鎖。這時候,A線程請求鎖,佔了鎖,把state+1

線程A取得了鎖,把 state原子性+1,這時候state被改爲1,A線程繼續執行其他任務,然後線程B請求鎖,線程B無法獲取鎖,生成節點進行排隊

初始化的時候,會生成一個空的頭節點,然後纔是B線程節點,這時候,如果線程A又請求鎖,是否需要排隊?答案當然是否定的,否則就直接死鎖了。當A再次請求鎖

可重入鎖:就是一個線程在獲取了鎖之後,再次去獲取了同一個鎖,這時候僅僅是把狀態值進行累加。如果線程A釋放了一次鎖

僅僅是把狀態值減了,只有線程A把此鎖全部釋放了,狀態值減到0了,其他線程纔有機會獲取鎖。當A把鎖完全釋放後,state恢復爲0,然後會通知隊列喚醒B線程節點,使B可以再次競爭鎖。當然,如果B線程後面還有C線程,C線程繼續休眠,除非B執行完了,通知了C線程。注意,當一個線程節點被喚醒然後取得了鎖,對應節點會從隊列中刪除

非公平鎖模型

當線程A執行完之後,要喚醒線程B是需要時間的,而且線程B醒來後還要再次競爭鎖,所以如果在切換過程當中,來了一個線程C,那麼線程C是有可能獲取到鎖的,如果C獲取到了鎖,B就只能繼續休眠了

爲什麼不默認是公平鎖

保證公平整體性能會比較低,其原因不是因爲檢查慢,而是因爲會讓活躍線程無法得到鎖,從而進入等待狀態,引起了頻繁的上下文切換,降低了整體的效率

ReentrantLock tryLock()方法使用的是非公平鎖

和synchronized比較

  • ReentrantLock可以實現與 synchronized 相同的語義 而且支持以非阻塞方式獲取鎖,也可以想用中斷,限時阻塞,更爲靈活;synchronized 的使用更爲簡單,代碼量也更少

  • synchronized 代表的是一種聲明式編程思維 由 Java 系統負責實現 程序員並不清楚實現細節;顯式鎖代表一種命令式編程思維,使用者需要實現所有的細節

  • 聲明式編程的好處除了簡單,在性能上也有所體現。 在較新版本的 JVM 上,ReentrantLock和synchronized的性能是接近的, 並且 Java 編譯器和虛擬機會不斷優化 synchronized 的實現,比如自動分析 synchronized 的使用,對於沒有鎖競爭的場景,自動忽略對獲取鎖/釋放鎖的調用

  • 能用 synchronized 就用 synchronized,不滿足使用要求的時候考慮使用 ReentrantLock

代碼資源

https://gitee.com/pingfanrenbiji/myconcurrent/blob/master/src/main/java/pers/hanchao/concurrent/reentrantLock/LockSupportTest.java

參考文章

https://blog.csdn.net/u011669700/article/details/80070892
https://blog.csdn.net/yanyan19880509/article/details/52345422
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章