天天看點

可重複鎖ReentrantLock原理分析

可重入鎖ReentrantLock實作層面依賴

一、CAS(compareAndSet)

LockSupport

基本的方法

可重複鎖ReentrantLock原理分析
park
park使得目前線程放棄cpu 進入等待(waiting)狀态 作業系統不會再對其進行排程

直到其他線程對它調用了unpark方法,其中unpark方法使得參數指定的線程恢複可運作狀态      
可重複鎖ReentrantLock原理分析

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

  • yield 隻是告訴作業系統可以讓其他線程先運作,但是自己可以仍是運作态
  • park 方法則是放棄線程的運作資格,使得線程進入 WAITING 等待狀态

[2] 響應中斷

park 方法是響應中斷的,當有中斷發生時,park方法會傳回,并且重新設定線程的中斷狀态      

[3]2個變體

  • parkNanos:可以指定等待的最長時間,參數是相對于目前時間的納秒數
  • parkUntil:可以指定最長等待的時間,參數是絕對時間,相對于紀元時的毫秒數
當等待逾時,方法就會傳回。同時還有一些其他的變體,可以指定一個對象,表示是由于該對象而進行等待,以便于調試,一般情況下傳遞的參數為 this      

getBlocker

可重複鎖ReentrantLock原理分析

二、AQS

  • 提供了一個state字段 被volatile修飾 保證記憶體可見性、順序性
可重複鎖ReentrantLock原理分析
  • AQS内部維護了一個等待隊列,借助CAS方法實作無阻塞算法進行更新

三、ReentrantLock

可重複鎖ReentrantLock原理分析
Sync是抽象類
NonfairSync是 fair 為 false 時使用的類[預設]
FairSync 是 fair 為 true 時需要使用的類      

lock實作

可重複鎖ReentrantLock原理分析
可重複鎖ReentrantLock原理分析
該方法被子類重寫      
可重複鎖ReentrantLock原理分析
可重複鎖ReentrantLock原理分析
如果沒有被鎖定,則使用CAS進行鎖定;如果目前線程已經被鎖定,則增加鎖定次數。

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

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

在當如等待隊列之後,調用 acquireQueued 來嘗試擷取鎖,其代碼為
      
可重複鎖ReentrantLock原理分析
是一個死循環,在每次循環中,首先檢查目前節點是否為第一個等待的節點,
如果是且能擷取到鎖,就将目前節點從等待隊列中移除并且傳回,
否則通過parkAndCheckInterrupt方法最終調用 LockSupport.park而放棄CPU,
進入等待狀态,在被喚醒之後檢查是否發生了中斷,記錄中斷标志。并且傳回中斷标志      
可重複鎖ReentrantLock原理分析
如果能獲得鎖則立即獲得,如若不能則加入等待隊列。被喚醒之後檢查自己是否為第一個等待的線程,如果是且能獲得鎖則傳回,否則繼續等待。如果在該過程中發生了中斷, lock 會記錄中斷标志位,但是不會提前傳回或者抛除異常      

unlock實作

可重複鎖ReentrantLock原理分析
可重複鎖ReentrantLock原理分析
tryRelease 方法會修改線程狀态并且釋放鎖, unparkSuccessor 方法會調用 LockSupport.unpark 将第一個等待的線程喚醒      

公平鎖和非公平鎖

公平鎖比非公平鎖在源碼實作上就多了一個檢查:當沒有其他等待時間更長的線程時,才能擷取到鎖      
可重複鎖ReentrantLock原理分析
公平鎖模型
初始化時, state=0,表示沒有線程過來搶鎖。這時候,A線程請求鎖,占了鎖,把state+1      
可重複鎖ReentrantLock原理分析
線程A取得了鎖,把 state原子性+1,這時候state被改為1,A線程繼續執行其他任務,然後線程B請求鎖,線程B無法擷取鎖,生成節點進行排隊      
可重複鎖ReentrantLock原理分析
初始化的時候,會生成一個空的頭節點,然後才是B線程節點,這時候,如果線程A又請求鎖,是否需要排隊?答案當然是否定的,否則就直接死鎖了。當A再次請求鎖      
可重複鎖ReentrantLock原理分析
可重入鎖:就是一個線程在擷取了鎖之後,再次去擷取了同一個鎖,這時候僅僅是把狀态值進行累加。如果線程A釋放了一次鎖      
可重複鎖ReentrantLock原理分析
僅僅是把狀态值減了,隻有線程A把此鎖全部釋放了,狀态值減到0了,其他線程才有機會擷取鎖。當A把鎖完全釋放後,state恢複為0,然後會通知隊列喚醒B線程節點,使B可以再次競争鎖。當然,如果B線程後面還有C線程,C線程繼續休眠,除非B執行完了,通知了C線程。注意,當一個線程節點被喚醒然後取得了鎖,對應節點會從隊列中删除      
可重複鎖ReentrantLock原理分析
非公平鎖模型
當線程A執行完之後,要喚醒線程B是需要時間的,而且線程B醒來後還要再次競争鎖,是以如果在切換過程當中,來了一個線程C,那麼線程C是有可能擷取到鎖的,如果C擷取到了鎖,B就隻能繼續休眠了      

為什麼不預設是公平鎖

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

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

和synchronized比較

  • ReentrantLock可以實作與 synchronized 相同的語義 而且支援以非阻塞方式擷取鎖,也可以想用中斷,限時阻塞,更為靈活;synchronized 的使用更為簡單,代碼量也更少
  • synchronized 代表的是一種聲明式程式設計思維 由 Java 系統負責實作 程式員并不清楚實作細節;顯式鎖代表一種指令式程式設計思維,使用者需要實作所有的細節
  • 聲明式程式設計的好處除了簡單,在性能上也有所展現。在較新版本的 JVM 上,ReentrantLock和synchronized的性能是接近的, 并且 Java 編譯器和虛拟機會不斷優化 synchronized 的實作,比如自動分析 synchronized 的使用,對于沒有鎖競争的場景,自動忽略對擷取鎖/釋放鎖的調用
  • 能用 synchronized 就用 synchronized,不滿足使用要求的時候考慮使用 ReentrantLock