LockSupport是用于建立鎖和其他同步類的阻塞原語。以下是jdk對LockSupport的描述。
Basic thread blocking primitives for
creating locks and other synchronization classes.
在《ReentrantLock詳解》(位址:https://yq.aliyun.com/articles/460711)中分析源碼的時候,我們就已經多次提到使用LockSupport的pack挂起線程,unpack喚醒被挂起的線程,此部落格将詳述LockSupport的原理以及實作。
LockSupport通過許可(permit)實作挂起線程、喚醒挂起線程功能。可以按照以下邏輯了解:
如果線程的permit存在,那麼線程不會被挂起,立即傳回;如果線程的permit不存在,認為線程缺少permit,是以需要挂起等待permit。
如果線程的permit不存在,那麼釋放一個permit。因為有permit了,是以如果線程處于挂起狀态,那麼此線程會被線程排程器喚醒。如果線程的permit存在,permit也不會累加,看起來想什麼事都沒做一樣。注意這一點和Semaphore是不同的。
主要功能:
如果許可存在,那麼将這個許可使用掉,并且立即傳回。如果許可不存在,那麼挂起目前線程,直到以下任意一件事情發生:
其他線程以目前線程為參數,調用unpark(thread)方法
其他線程通過Thread#interrupt中斷目前線程。
Pack方法沒有原因的傳回(調用時應該重新檢查導緻線程暫停的條件)。這一點後面解釋。
LockSupport中pack有多個版本,如下所示:
park(Object)
挂起目前線程,具體見上面pack的源碼分析
parkNanos(Object,
long)
指定了一個挂起時間(相對于目前的時間),時間到後自動被喚醒;例如1000納秒後自動喚醒
parkUntil(Object,
指定一個挂起時間(絕對時間),時間到後自動被喚醒;例如2018-02-12 21點整自動被喚醒。
park()
和park(Object)相比少了挂起前為線程設定blocker、被喚醒後清理blocker的操作。
parkNanos(long)
和parkNanos(Object, long)類似,僅少了blocker相關的操作
parkUntil(long)
和parkUntil(Object, long)類似,僅少了blocker相關的操作
從上面表格可以看出,park支援blocker對象作為參數。此blocker對象線上程受阻塞時被記錄,這樣監視工具和診斷工具就可以确定線程受阻塞的原因。建議最好使用這些帶blocker的方法版本,而不是不帶blocker參數的方法。
設定線程許可為可用。
如果線程目前已經被pack挂起,那麼這個線程将會被喚醒。
如果線程目前沒有被挂起,那麼下次調用pack不會挂起線程。
挂起與阻塞主要的差別應該說是它們面向的對象不同。對線程來說, LockSupport的park/unpark更符合阻塞和喚醒的語義,他們以“線程”作為方法的參數,語義更清晰,使用起來也更友善。而wait/notify使“線程”的阻塞/喚醒對線程本身來說是被動的,要準确的控制哪個線程、什麼時候阻塞/喚醒是很困難的,是以是要麼随機喚醒一個線程(notify)要麼喚醒所有的(notifyAll)。
park和unpark方法不會出現Thread.suspend和Thread.resume的死鎖問題。這是因為許可的存在,調用park的線程和另一個試圖将其unpark的線程之間的将沒有競争關系。此外,如果線程被中斷或者逾時,則park将傳回。
park方法還可以在其他任何時間“毫無理由”地傳回,是以通常必須在重新檢查傳回條件的循環裡調用此方法。從這個意義上說,park 是“忙碌等待”的一種優化,它不會浪費這麼多的時間進行自旋,但是必須将它與unpark配對使用才更高效。
以下僞代碼是pack的常用模型。
以下是ReentrantLock中pack的使用代碼