線程間的同步與通訊(4)——Lock 和 Condtion

前言

系列文章目錄 java

前面幾篇咱們學習了synchronized同步代碼塊,瞭解了java的內置鎖,並學習了監視器鎖的wait/notify機制。在大多數狀況下,內置鎖都能很好的工做,但它在功能上存在一些侷限性,例如沒法實現非阻塞結構的加鎖規則等。爲了拓展同步代碼塊中的監視器鎖,java 1.5 開始,出現了lock接口,它實現了可定時、可輪詢與可中斷的鎖獲取操做,公平隊列,以及非塊結構的鎖。segmentfault

與內置鎖不一樣,Lock是一種顯式鎖,它更加「危險」,由於在程序離開被鎖保護的代碼塊時,不會像監視器鎖那樣自動釋放,須要咱們手動釋放鎖。因此,在咱們使用lock鎖時,必定要記得:
在finally塊中調用lock.unlock()手動釋放鎖!!!
在finally塊中調用lock.unlock()手動釋放鎖!!!
在finally塊中調用lock.unlock()手動釋放鎖!!!工具

Lock接口

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    void unlock();
    
    Condition newCondition();
}

典型的使用方式:學習

Lock l = ...;
l.lock();
try {
    // access the resource protected by this lock
} finally {
    l.unlock();
}

鎖的獲取

Lock接口定義了四種獲取鎖的方式,下面咱們一個個來看this

  • lock()spa

    • 阻塞式獲取,在沒有獲取到鎖時,當前線程將會休眠,不會參與線程調度,直到獲取到鎖爲止,獲取鎖的過程當中不響應中斷
  • lockInterruptibly()線程

    • 阻塞式獲取,而且可中斷,該方法將在如下兩種狀況之一發生的狀況下拋出InterruptedExceptioncode

      • 在調用該方法時,線程的中斷標誌位已經被設爲true了
      • 在獲取鎖的過程當中,線程被中斷了,而且鎖的獲取實現會響應這個中斷
    • 在InterruptedException拋出後,當前線程的中斷標誌位將會被清除
  • tryLock()對象

    • 非阻塞式獲取,從名字中也能夠看出,try就是試一試的意思,不管成功與否,該方法都是當即返回的
    • 相比前面兩種阻塞式獲取的方式,該方法是有返回值的,獲取鎖成功了則返回true,獲取鎖失敗了則返回false
  • tryLock(long time, TimeUnit unit)blog

    • 帶超時機制,而且可中斷
    • 若是能夠獲取帶鎖,則當即返回true
    • 若是獲取不到鎖,則當前線程將會休眠,不會參與線程調度,直到如下三個條件之一被知足:

      • 當前線程獲取到了鎖
      • 其它線程中斷了當前線程
      • 設定的超時時間到了
    • 該方法將在如下兩種狀況之一發生的狀況下拋出InterruptedException

      • 在調用該方法時,線程的中斷標誌位已經被設爲true了
      • 在獲取鎖的過程當中,線程被中斷了,而且鎖的獲取實現會響應這個中斷
    • 在InterruptedException拋出後,當前線程的中斷標誌位將會被清除
    • 若是超時時間到了,當前線程尚未得到鎖,則會直接返回false(注意,這裏並無拋出超時異常)

其實,tryLock(long time, TimeUnit unit)更像是阻塞式與非阻塞式的結合體,即在必定條件下(超時時間內,沒有中斷髮生)阻塞,不知足這個條件則當即返回(非阻塞)。

這裏把四種鎖的獲取方式總結以下:
鎖的獲取

鎖的釋放

相對於鎖的獲取,鎖的釋放的方法就簡單的多,只有一個

void unlock();

值得注意的是,只有擁有的鎖的線程才能釋放鎖,而且,必須顯式地釋放鎖,這一點和離開同步代碼塊就自動被釋放的監視器鎖是不一樣的。

newCondition

Lock接口還定義了一個newCondition方法:

Condition newCondition();

該方法將建立一個綁定在當前Lock對象上的Condition對象,這說明Condition對象和Lock對象是對應的,一個Lock對象能夠建立多個Condition對象,它們是一個對多的關係。

Condition 接口

上面咱們說道,Lock接口中定義了newCondition方法,它返回一個關聯在當前Lock對象上的Condition對象,下面咱們來看看這個Condition對象是個啥。

每個新工具的出現老是爲了解決必定的問題,Condition接口的出現也不例外。
若是說Lock接口的出現是爲了拓展示有的監視器鎖,那麼Condition接口的出現就是爲了拓展同步代碼塊中的wait, notify機制。

監視器鎖的 wait/notify 機制的弊端

一般狀況下,咱們調用wait方法,主要是由於必定的條件沒有知足,咱們把須要知足的事件或條件稱做條件謂詞。

而另外一方面,由前面幾篇介紹synchronized原理的文章咱們知道,全部調用了wait方法的線程,都會在同一個監視器鎖的wait set中等待,這看上去很合理,可是倒是該機制的短板所在——全部的線程都等待在同一個notify方法上(notify方法指notify()notifyAll()兩個方法,下同)。每個調用wait方法的線程可能等待在不一樣的條件謂詞上,可是有時候即便本身等待的條件並無知足,線程也有可能被「別的線程的」notify方法喚醒,由於你們用的是同一個監視器鎖。這就比如一個班上有幾個重名的同窗(使用相同的監視器鎖),老師喊了這個名字(notify方法),結果這幾個同窗全都站起來了(等待在監視器鎖上的線程都被喚醒了)。

這樣以來,即便本身被喚醒後,搶到了監視器鎖,發現其實條件仍是不知足,仍是得調用wait方法掛起,就致使了不少無心義的時間和CPU資源的浪費。

這一切的根源就在於咱們在調用wait方法時沒有辦法來指明到底是在等待什麼樣的條件謂詞上,所以喚醒時,也不知道該喚醒誰,只能把全部的線程都喚醒了。

所以,最好的方式是,咱們在掛起時就指明瞭在什麼樣的條件謂詞上掛起,同時,在等待的事件發生後,只喚醒等待在這個事件上的線程,而實現了這個思路的就是Condition接口。

有了Condition接口,咱們就能夠在同一個鎖上建立不一樣的喚醒條件,從而在必定條件謂詞知足後,有針對性的喚醒特定的線程,而不是一股腦的將全部等待的線程都喚醒。

Condition的 await/signal 機制

既然前面說了Condition接口的出現是爲了拓展示有的wait/notify機制,那咱們就先來看看現有的wait/notify機制有哪些方法:

public class Object {
    public final void wait() throws InterruptedException {
        wait(0);
    }
    public final native void wait(long timeout) throws InterruptedException;
    public final void wait(long timeout, int nanos) throws InterruptedException {
        // 這裏省略方法的實現
    }
    public final native void notify();
    public final native void notifyAll();
}

接下來咱們再看看Condition接口有哪些方法:

public interface Condition {
    void await() throws InterruptedException;
    long awaitNanos(long nanosTimeout) throws InterruptedException;
    boolean await(long time, TimeUnit unit) throws InterruptedException;
    
    void awaitUninterruptibly();
    boolean awaitUntil(Date deadline) throws InterruptedException;
    
    void signal();
    void signalAll();
}

對比發現,這裏存在明顯的對應關係:

Object 方法 Condition 方法 區別
void wait() void await()
void wait(long timeout) long awaitNanos(long nanosTimeout) 時間單位,返回值
void wait(long timeout, int nanos) boolean await(long time, TimeUnit unit) 時間單位,參數類型,返回值
void notify() void signal()
void notifyAll() void signalAll()
- void awaitUninterruptibly() Condition獨有
- boolean awaitUntil(Date deadline) Condition獨有

它們在接口的規範上都是差很少的,只不過wait/notify機制針對的是全部在監視器鎖的wait set中的線程,而await/signal機制針對的是全部等待在該Condition上的線程。

這裏多說一句,在接口的規範中,wait(long timeout)的時間單位是毫秒(milliseconds), 而awaitNanos(long nanosTimeout)的時間單位是納秒(nanoseconds), 就這一點而言,awaitNanos這個方法名其實語義上更清晰,而且相對於wait(long timeout, int nanos)這個略顯雞肋的方法(以前的分析中咱們已經吐槽過這個方法的實現了),await(long time, TimeUnit unit)這個方法就顯得更加直觀和有效。

另一點值得注意的是,awaitNanos(long nanosTimeout)有返回值的,它返回了剩餘等待的時間;await(long time, TimeUnit unit)也是有返回值的,若是該方法是由於超時時間到了而返回的,則該方法返回false, 不然返回true。

你們有沒有覺的奇怪,一樣是帶超時時間的等待,爲何wait方式沒有返回值,await方式有返回值呢。
存在即合理,既然多加了返回值,天然是有它的用意,那麼這個多加的返回值有什麼用呢?

咱們知道,當一個線程從帶有超時時間的wait/await方法返回時,必然是發生瞭如下4種狀況之一:

  1. 其餘線程調用了notify/signal方法,而且當前線程剛好是被選中來喚醒的那一個
  2. 其餘線程調用了notifyAll/signalAll方法
  3. 其餘線程中斷了當前線程
  4. 超時時間到了

其中,第三條會拋出InterruptedException,是比較容易分辨的;除去這個,當wait方法返回後,咱們其實沒法區分它是由於超時時間到了返回了,仍是被notify返回的。可是對於await方法,由於它是有返回值的,咱們就可以經過返回值來區分:

  • 若是awaitNanos(long nanosTimeout)的返回值大於0,說明超時時間還沒到,則該返回是由signal行爲致使的
  • 若是await(long time, TimeUnit unit)返回true, 說明超時時間還沒到,則該返回是由signal行爲致使的

源碼的註釋也說了,await(long time, TimeUnit unit)至關於調用awaitNanos(unit.toNanos(time)) > 0

因此,它們的返回值可以幫助咱們弄清楚方法返回的緣由

Condition接口中還有兩個在Object中找不到對應的方法:

void awaitUninterruptibly();
boolean awaitUntil(Date deadline) throws InterruptedException;

前面說的全部的wait/await方法,它們方法的簽名中都拋出了InterruptedException,說明他們在等待的過程當中都是響應中斷的,awaitUninterruptibly方法從名字中就能夠看出,它在等待鎖的過程當中是不響應中斷的,因此沒有InterruptedException拋出。也就是說,它會一直阻塞,直到signal/signalAll被調用。若是在這過程當中線程被中斷了,它並不響應這個中斷,只是在該方法返回的時候,該線程的中斷標誌位將是true, 調用者能夠檢測這個中斷標誌位以輔助判斷在等待過程當中是否發生了中斷,以此決定要不要作額外的處理。

boolean awaitUntil(Date deadline)boolean await(long time, TimeUnit unit) 其實做用是差很少的,返回值表明的含義也同樣,只不過一個是相對時間,一個是絕對時間,awaitUntil方法的參數是Date,表示了一個絕對的時間,即截止日期,在這個日期以前,該方法會一直等待,除非被signal或者被中斷。

至此,Lock接口和Condition接口咱們就分析完了。

咱們將在下一篇中給出Lock接口的具體實現的例子,在逐行分析AQS源碼(4)——Condition接口實現中給出Condition接口具體實現的例子。

(完)

系列文章目錄

相關文章
相關標籤/搜索