系列文章目錄 java
前面幾篇咱們學習了synchronized同步代碼塊,瞭解了java的內置鎖,並學習了監視器鎖的wait/notify機制。在大多數狀況下,內置鎖都能很好的工做,但它在功能上存在一些侷限性,例如沒法實現非阻塞結構的加鎖規則等。爲了拓展同步代碼塊中的監視器鎖,java 1.5 開始,出現了lock接口,它實現了可定時、可輪詢與可中斷的鎖獲取操做,公平隊列,以及非塊結構的鎖。segmentfault
與內置鎖不一樣,Lock是一種顯式鎖,它更加「危險」,由於在程序離開被鎖保護的代碼塊時,不會像監視器鎖那樣自動釋放,須要咱們手動釋放鎖。因此,在咱們使用lock鎖時,必定要記得:
在finally塊中調用lock.unlock()手動釋放鎖!!!
在finally塊中調用lock.unlock()手動釋放鎖!!!
在finally塊中調用lock.unlock()手動釋放鎖!!!工具
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
tryLock()對象
tryLock(long time, TimeUnit unit)blog
若是獲取不到鎖,則當前線程將會休眠,不會參與線程調度,直到如下三個條件之一被知足:
該方法將在如下兩種狀況之一發生的狀況下拋出InterruptedException
其實,tryLock(long time, TimeUnit unit)
更像是阻塞式與非阻塞式的結合體,即在必定條件下(超時時間內,沒有中斷髮生)阻塞,不知足這個條件則當即返回(非阻塞)。
這裏把四種鎖的獲取方式總結以下:
相對於鎖的獲取,鎖的釋放的方法就簡單的多,只有一個
void unlock();
值得注意的是,只有擁有的鎖的線程才能釋放鎖,而且,必須顯式地釋放鎖,這一點和離開同步代碼塊就自動被釋放的監視器鎖是不一樣的。
Lock接口還定義了一個newCondition方法:
Condition newCondition();
該方法將建立一個綁定在當前Lock對象上的Condition對象,這說明Condition對象和Lock對象是對應的,一個Lock對象能夠建立多個Condition對象,它們是一個對多的關係。
上面咱們說道,Lock接口中定義了newCondition
方法,它返回一個關聯在當前Lock對象上的Condition對象,下面咱們來看看這個Condition對象是個啥。
每個新工具的出現老是爲了解決必定的問題,Condition接口的出現也不例外。
若是說Lock接口的出現是爲了拓展示有的監視器鎖,那麼Condition接口的出現就是爲了拓展同步代碼塊中的wait, notify機制。
一般狀況下,咱們調用wait方法,主要是由於必定的條件沒有知足,咱們把須要知足的事件或條件稱做條件謂詞。
而另外一方面,由前面幾篇介紹synchronized原理的文章咱們知道,全部調用了wait方法的線程,都會在同一個監視器鎖的wait set
中等待,這看上去很合理,可是倒是該機制的短板所在——全部的線程都等待在同一個notify方法上(notify方法指notify()
和notifyAll()
兩個方法,下同)。每個調用wait方法的線程可能等待在不一樣的條件謂詞上,可是有時候即便本身等待的條件並無知足,線程也有可能被「別的線程的」notify方法喚醒,由於你們用的是同一個監視器鎖。這就比如一個班上有幾個重名的同窗(使用相同的監視器鎖),老師喊了這個名字(notify方法),結果這幾個同窗全都站起來了(等待在監視器鎖上的線程都被喚醒了)。
這樣以來,即便本身被喚醒後,搶到了監視器鎖,發現其實條件仍是不知足,仍是得調用wait方法掛起,就致使了不少無心義的時間和CPU資源的浪費。
這一切的根源就在於咱們在調用wait方法時沒有辦法來指明到底是在等待什麼樣的條件謂詞上,所以喚醒時,也不知道該喚醒誰,只能把全部的線程都喚醒了。
所以,最好的方式是,咱們在掛起時就指明瞭在什麼樣的條件謂詞上掛起,同時,在等待的事件發生後,只喚醒等待在這個事件上的線程,而實現了這個思路的就是Condition接口。
有了Condition接口,咱們就能夠在同一個鎖上建立不一樣的喚醒條件,從而在必定條件謂詞知足後,有針對性的喚醒特定的線程,而不是一股腦的將全部等待的線程都喚醒。
既然前面說了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種狀況之一:
其中,第三條會拋出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接口具體實現的例子。
(完)