天天看點

LockSupport

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的使用代碼