【Java併發工具類】Lock和Condition
前言
Java SDK併發包通過Lock
和Condition
兩個介面來實現管程,其中Lock用於解決互斥問題,Condition用於解決同步問題。我們需要知道,Java語言本身使用synchronized
實現了管程的,那麼為什麼還在SDK中提供另外一種實現呢?欲知為何請看下文。
下面將先闡述再造管程的理由,然後詳細介紹Lock和Condition,最後再看實現同步機制時是選擇synchronized還是SDK中的管程。
再造管程的理由
Java本就從語言層面實現了管程,然而後面又在SDK中再次現實,這隻能說明語言層面的實現的管程有所不足。要說談synchronized的不足,我們就要要回顧一下破壞死鎖的不可搶佔問題:
破壞不可搶佔條件,需要執行緒在獲取不到鎖的情況下主動釋放它擁有的資源。當我們使用synchronized的時候,執行緒是沒有辦法主動釋放它佔有的資源的。因為,synchronized在申請不到資源時,會使執行緒直接進入阻塞狀態,而執行緒進入了阻塞狀態就不能主動釋放佔有的資源。
所以,有沒有一種辦法可以使得執行緒處於阻塞狀態時也能夠響應中斷主動釋放資源或者獲取不到資源的時候不阻塞呢?答案是有的,使用SDK中的管程。
SDK中管程的實現java.util.concurrent
中的Lock
介面,提供瞭如下三種設計思想都可以解決死鎖的不可搶佔條件:
能夠響應中斷
執行緒處於阻塞狀態時可以接收中斷訊號。我們便可以給阻塞的執行緒傳送中斷訊號,喚醒執行緒,執行緒便有機會釋放它曾經擁有的鎖。這樣便可破壞不可搶佔條件。
支援超時
如果執行緒在一段時間之內沒有獲取到鎖,不是進入阻塞狀態,而是返回一個錯誤,那這個執行緒也有機會釋放曾經持有的鎖。這樣也能破壞不可搶佔條件。
非阻塞地獲取鎖
如果嘗試獲取鎖失敗,並不進入阻塞狀態,而是直接返回,那這個執行緒也有機會釋放曾經持有的鎖。這樣也可以破壞不可搶佔條件。
這三種方案就可全面彌補synchronized的問題。也就是再造管程的原因。這三種思想體現在Lock介面的API上,便是如下三個方法:
// 支援中斷的 API void lockInterruptibly() throws InterruptedException; // 支援超時的 API boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // 支援非阻塞獲取鎖的 API boolean tryLock();
下面我們便繼續介紹Lock。
Lock和ReentrantLock
Lock介面中定義了一組抽象的加鎖操作:
public interface Lock{
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition(); // 關聯Condition物件使用
}
與synchronized內建加鎖不同,Lock提供的是無條件的、可輪詢的、定時的以及可中斷的鎖獲取操作,所有加鎖和解鎖都是顯式的。在Lock的實現中必須要提供與內建鎖相同的記憶體可見性語義,但是加鎖語義、排程演算法、順序保證以及效能等方面可以不同。
ReentrantLock
實現了Lock介面,並提供了與synchronized相同的互斥性和記憶體可見性。在獲取ReentrantLock時,有著進入同步程式碼塊相同的記憶體語義,在釋放ReentrantLock時,同樣有著與退出同步程式碼塊相同的記憶體語義。見名知義,ReentrantLock還提供了同synchronized一樣的可重入加鎖的語義。