1. 程式人生 > >Java併發讀書筆記:Lock與ReentrantLock

Java併發讀書筆記:Lock與ReentrantLock

Lock位於java.util.concurrent.locks包下,是一種執行緒同步機制,就像synchronized塊一樣。但是,Locksynchronized塊更靈活、更復雜。

話不多說,我們直接來看官方文件對Lock介面相關概念及功能的描述,今天又是看英文文件,翻譯理解的一天。

一、Lock繼承關係

二、官方文件解讀


三、Lock介面方法解讀

void lock()

獲取鎖。如果鎖不可用,則當前執行緒將出於執行緒排程目的而禁用,並處於休眠狀態,直到獲得鎖為止。

void lockInterruptibly() throws InterruptedException;

如果當前執行緒未被中斷,則獲取鎖。如果鎖可用,則獲取鎖並立即返回。

如果鎖不可用,出於執行緒排程目的,將禁用當前執行緒,該執行緒將一直處於休眠狀態。

下面兩種情形會讓當前執行緒停止休眠狀態:

  • 鎖由當前執行緒獲取。

  • 其他一些執行緒中斷當前執行緒,並且支援對鎖獲取的中斷。

當前執行緒出現下面兩種情況時,將丟擲InterruptedException,並清除當前執行緒的中斷狀態。

  • 當前執行緒在進入此方法時,已經設定為中斷狀態。

  • 當前執行緒在獲取鎖時被中斷,並且支援對鎖獲取中斷。

boolean tryLock();

嘗試獲取鎖,如果鎖處於空閒狀態,則獲取鎖,並立即返回true。如果鎖不可用,則立即返回false。

該方法的典型使用:

    Lock lock = ...;
    //確保鎖在被獲取時被解鎖
    if (lock.tryLock()) {
        try {
            // manipulate protected state
        } finally {
            lock.unlock();
        }
    } else {
        // perform alternative actions
    }

boolean tryLock(long time, TimeUnit unit) throws
InterruptedException;

該方法為tryLock()的過載方法,兩個引數分別表示為:

  • time:等待鎖的最長時間
  • unit:時間單位

如果在給定的等待時間內是空閒的並且當前執行緒沒有被中斷,則獲取鎖。如果鎖可用,則此方法立即獲取鎖並返回true,如果鎖不可用,出於執行緒排程目的,將禁用當前執行緒,該執行緒將一直處於休眠狀態。

下面三種情形會讓當前執行緒停止休眠狀態:

  • 鎖由當前執行緒獲取。

  • 其他一些執行緒中斷當前執行緒,並且支援對鎖獲取的中斷。
  • 到了指定的等待時間。

當前執行緒出現下面兩種情況時,將丟擲InterruptedException,並清除當前執行緒的中斷狀態。

  • 當前執行緒在進入此方法時,已經設定為中斷狀態。

  • 當前執行緒在獲取鎖時被中斷,並且支援對鎖獲取中斷。

如果指定的等待時間超時,則返回false值。如果時間小於或等於0,則該方法永遠不會等待。

void unlock()

釋放鎖,與lock()、tryLock()、tryLock(long , TimeUnit)、lockInterruptibly()相對應。

Condition newCondition()

返回繫結到此鎖例項的Condition例項。當前執行緒只有獲得了鎖,才能呼叫Condition例項的await()方法,並釋放鎖。

四、重要實現類ReentrantLock

顧名思義,ReentrantLock是重入鎖,關於這個重入鎖,之前涉及過一些知識,在這裡做整合,並稍微地補充一下。

ReentrantLock位於java.util.concurrent(J.U.C)包下,是Lock介面的實現類。基本用法與synchronized相似,都具備可重入互斥的特性,但擁有擴充套件的功能。

RenntrantLock推薦的基本寫法:

class X {
    //定義鎖物件
    private final ReentrantLock lock = new ReentrantLock();
    // ...
    //定義需要保證執行緒安全的方法
    public void m() {
        //加鎖
        lock.lock();  
        try{
        // 保證執行緒安全的程式碼
        }
        // 使用finally塊保證釋放鎖
        finally {
            lock.unlock()
        }
    }
}

1、API層面的鎖

ReentrantLock表現為API層面的互斥鎖,通過lock()unlock()方法完成,是顯式的,而synchronized表現為原生語法層面的互斥鎖,是隱式的。

2、可重入的

重進入意味著:任意執行緒在獲取到鎖之後能夠再次獲取該鎖而不會被鎖阻塞,synchronized和Reentrant都是可重入的,隱式顯式之分。

實現可重入需要解決的兩個關鍵部分:

  1. 鎖需要去識別獲取鎖的執行緒是否是當前佔據鎖的執行緒,如果是的話,就成功獲取。
  2. 鎖獲取一次,內部鎖計數器需要加一,釋放一次減一,計數為零表示為成功釋放鎖。

3、可公平的

關於鎖公平的部分,官方文件是這樣描述的(英文我就不貼了),詞彙較簡單,我試著翻譯一下:

Reentrant類的建構函式接受一個可選的公平性引數fair。這時候就出現兩種選擇:

  • 公平的(fair == true):保證等待時間最長的執行緒優先獲取鎖,即FIFO。
  • 非公平的(fair == false):此鎖不保證任何特定的訪問順序。

公平鎖往往體現處的總體吞吐量比非公平鎖要低,也就是更慢。

鎖的公平性並不保證執行緒排程的公平性,但公平鎖能夠減少"飢餓"發生的概率。

需要注意的是:不定時的tryLock()方法不支援公平性設定。如果鎖可用,即使其他執行緒等待時間比它長,它也會成功獲得鎖。

4、等待可中斷

當持有執行緒長期不釋放鎖的時候,正在等待的執行緒可以選擇放棄等待或處理其他事情。

5、鎖繫結

一個ReentrantLock物件可以通過newCondition()同時繫結多個Condition物件。


JDK1.6之前,ReentrantLock在效能方面是要領先於synchronized鎖的,但是JDK1.6及之後版本實現了各種鎖優化技術,可參考:
聊聊併發Java SE1.6中的Synchronized,後續效能改進會更加偏向於原生的synchronized。


參考資料:
《深入理解Java虛擬機器》周志明
《Java併發程式設計的藝術》方騰