java.util.concurrent.locks包下Lock,ReadWriteLock,Condition接口源碼閱讀

  • Lock接口
    Lock 實現提供了比使用 synchronized 方法和語句可獲得的更廣泛的鎖定操作。此實現允許更靈活的結構,可以具有差別很大的屬性,可以支持多個相關的 Condition 對象。

鎖是控制多個線程對共享資源進行訪問的工具。通常,鎖提供了對共享資源的獨占訪問。一次只能有一個線程獲得鎖,對共享資源的所有訪問都需要首先獲得鎖。不過,某些鎖可能允許對共享資源并發訪問,如 ReadWriteLock的讀取鎖。

synchronized 方法或語句的使用提供了對與每個對象相關的隱式監視器鎖的訪問,但卻強制所有鎖獲取和釋放均要出現在一個塊結構中:當獲取了多個鎖時,它們必須以相反的順序釋放,且必須在與所有鎖被獲取時相同的詞法范圍內釋放所有鎖。

雖然 synchronized 方法和語句的范圍機制使得使用監視器鎖編程方便了很多,而且還幫助避免了很多涉及到鎖的常見編程錯誤,但有時也需要以更為靈活的方式使用鎖。例如,某些遍歷并發訪問的數據結果的算法要求使用 "hand-over-hand" 或 "chain locking":獲取節點 A 的鎖,然后再獲取節點 B 的鎖,然后釋放 A 并獲取 C,然后釋放 B 并獲取 D,依此類推。Lock 接口的實現允許鎖在不同的作用范圍內獲取和釋放,并允許以任何順序獲取和釋放多個鎖,從而支持使用這種技術。

隨著靈活性的增加,也帶來了更多的責任。不使用塊結構鎖就失去了使用 synchronized 方法和語句時會出現的鎖自動釋放功能。在大多數情況下,應該使用以下語句:

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

鎖定和取消鎖定出現在不同作用范圍中時,必須謹慎地確保保持鎖定時所執行的所有代碼用 try-finally 或 try-catch 加以保護,以確保在必要時釋放鎖。

Lock 實現提供了使用 synchronized 方法和語句所沒有的其他功能,包括提供了一個非塊結構的獲取鎖嘗試 tryLock()、一個獲取可中斷鎖的嘗試 lockInterruptibly()和一個獲取超時失效鎖的嘗試tryLock(long, TimeUnit)

Lock 類還可以提供與隱式監視器鎖完全不同的行為和語義,如保證排序、非重入用法或死鎖檢測。如果某個實現提供了這樣特殊的語義,則該實現必須對這些語義加以記錄。

注意,Lock 實例只是普通的對象,其本身可以在 synchronized 語句中作為目標使用。獲取 Lock 實例的監視器鎖與調用該實例的任何 lock() 方法沒有特別的關系。為了避免混淆,建議除了在其自身的實現中之外,決不要以這種方式使用 Lock 實例。

除非另有說明,否則為任何參數傳遞 null 值都將導致拋出 NullPointerException

內存同步

所有 Lock 實現都必須 實施與內置監視器鎖提供的相同內存同步語義,如 The Java Language Specification, Third Edition (17.4 Memory Model) 中所描述的:

  • 成功的 lock 操作與成功的 Lock 操作具有同樣的內存同步效應。
  • 成功的 unlock 操作與成功的 Unlock 操作具有同樣的內存同步效應。

不成功的鎖定與取消鎖定操作以及重入鎖定/取消鎖定操作都不需要任何內存同步效果。

實現注意事項

三種形式的鎖獲取(可中斷、不可中斷和定時)在其性能特征、排序保證或其他實現質量上可能會有所不同。而且,對于給定的 Lock 類,可能沒有中斷正在進行的 鎖獲取的能力。因此,并不要求實現為所有三種形式的鎖獲取定義相同的保證或語義,也不要求其支持中斷正在進行的鎖獲取。實現必需清楚地對每個鎖定方法所提供的語義和保證進行記錄。還必須遵守此接口中定義的中斷語義,以便為鎖獲取中斷提供支持:完全支持中斷,或僅在進入方法時支持中斷。

由于中斷通常意味著取消,而通常又很少進行中斷檢查,因此,相對于普通方法返回而言,實現可能更喜歡響應某個中斷。即使出現在另一個操作后的中斷可能會釋放線程鎖時也是如此。實現應記錄此行為。


public interface Lock {

    /**
     * 獲取鎖。
     * 如果鎖不可用,出于線程調度目的,將禁用當前線程,并且在獲得鎖之前,該線程將一直處于休眠狀態。
     *
     * 實現注意事項:
     * Lock 實現可能能夠檢測到鎖的錯誤使用,比如會導致死鎖的調用,在那種環境下還可能拋出一個 (unchecked) 異常。Lock 實現必須對環境和異常類型進行記錄。
     */
    void lock();

    /**
     * 如果當前線程未被 中斷,則獲取鎖。
     * 如果鎖可用,則獲取鎖,并立即返回。
     *
     * 如果鎖不可用,出于線程調度目的,將禁用當前線程,并且在發生以下兩種情況之一以前,該線程將一直處于休眠狀態:
     *
     * 鎖由當前線程獲得;或者
     * 其他某個線程中斷當前線程,并且支持對鎖獲取的中斷。
     * 如果當前線程:
     *
     * 在進入此方法時已經設置了該線程的中斷狀態;或者
     * 在獲取鎖時被中斷,并且支持對鎖獲取的中斷,
     * 則將拋出 InterruptedException,并清除當前線程的已中斷狀態。
     * 實現注意事項
     *
     * 在某些實現中可能無法中斷鎖獲取,即使可能,該操作的開銷也很大。程序員應該知道可能會發生這種情況。在這種情況下,該實現應該對此進行記錄。
     *
     * 相對于普通方法返回而言,實現可能更喜歡響應某個中斷。
     *
     * Lock 實現可能可以檢測鎖的錯誤用法,例如,某個調用可能導致死鎖,在特定的環境中可能拋出(未經檢查的)異常。該 Lock 實現必須對環境和異常類型進行記錄。
     */
    void lockInterruptibly() throws InterruptedException;

    /**
     * 僅在調用時鎖為空閑狀態才獲取該鎖。
     * 如果鎖可用,則獲取鎖,并立即返回值 true。如果鎖不可用,則此方法將立即返回值 false。
     *
     * 此方法的典型使用語句如下:
     *
     *       Lock lock = ...;
     *       if (lock.tryLock()) {
     *           try {
     *               // manipulate protected state
     *           } finally {
     *               lock.unlock();
     *           }
     *       } else {
     *           // perform alternative actions
     *       }
     *
     * 此用法可確保如果獲取了鎖,則會釋放鎖,如果未獲取鎖,則不會試圖將其釋放。
     * 返回:
     * 如果獲取了鎖,則返回 true;否則返回 false。
     */
    boolean tryLock();

    /**
     *如果鎖在給定的等待時間內空閑,并且當前線程未被 中斷,則獲取鎖。
     * 如果鎖可用,則此方法將立即返回值 true。如果鎖不可用,出于線程調度目的,將禁用當前線程,并且在發生以下三種情況之一前,該線程將一直處于休眠狀態:
     *
     * 鎖由當前線程獲得;或者
     * 其他某個線程中斷當前線程,并且支持對鎖獲取的中斷;或者
     * 已超過指定的等待時間
     * 如果獲得了鎖,則返回值 true。
     *
     * 如果當前線程:
     *
     * 在進入此方法時已經設置了該線程的中斷狀態;或者
     * 在獲取鎖時被中斷,并且支持對鎖獲取的中斷,
     * 則將拋出 InterruptedException,并會清除當前線程的已中斷狀態。
     * 如果超過了指定的等待時間,則將返回值 false。如果 time 小于等于 0,該方法將完全不等待。
     *
     * 實現注意事項
     *
     * 在某些實現中可能無法中斷鎖獲取,即使可能,該操作的開銷也很大。程序員應該知道可能會發生這種情況。在這種情況下,該實現應該對此進行記錄。
     *
     * 相對于普通方法返回而言,實現可能更喜歡響應某個中斷,或者報告出現超時情況。
     *
     * Lock 實現可能可以檢測鎖的錯誤用法,例如,某個調用可能導致死鎖,在特定的環境中可能拋出(未經檢查的)異常。該 Lock 實現必須對環境和異常類型進行記錄。
     *
     * 參數:
     * time - 等待鎖的最長時間
     * unit - time 參數的時間單位
     * 返回:
     * 如果獲得了鎖,則返回 true;如果在獲取鎖前超過了等待時間,則返回 false
     * 拋出:
     * InterruptedException - 如果在獲取鎖時,當前線程被中斷(并且支持對鎖獲取的中斷)
     */
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    /**
     *釋放鎖。
     * 實現注意事項
     *
     * Lock 實現通常對哪個線程可以釋放鎖施加了限制(通常只有鎖的保持者可以釋放它),如果違背了這個限制,可能會拋出(未經檢查的)異常。該 Lock 實現必須對所有限制和異常類型進行記錄。
     */
    void unlock();

    /**
     *返回綁定到此 Lock 實例的新 Condition 實例。
     * 在等待條件前,鎖必須由當前線程保持。調用 Condition.await() 將在等待前以原子方式釋放鎖,并在等待返回前重新獲取鎖。
     *
     * 實現注意事項
     *
     * Condition 實例的具體操作依賴于 Lock 實現,并且該實現必須對此加以記錄。
     *
     * 返回:
     * 用于此 Lock 實例的新 Condition 實例
     * 拋出:
     * UnsupportedOperationException - 如果此 Lock 實現不支持條件
     */
    Condition newCondition();
}
  • ReadWriteLock 接口
    ReadWriteLock 維護了一對相關的鎖,一個用于只讀操作,另一個用于寫入操作。只要沒有 writer,讀取鎖可以由多個 reader 線程同時保持。寫入鎖是獨占的。
    ???所有ReadWriteLock 實現都必須保證 writeLock操作的內存同步效果也要保持與相關readLock的聯系。也就是說,成功獲取讀鎖的線程會看到寫入鎖之前版本所做的所有更新。
    ???與互斥鎖相比,讀-寫鎖允許對共享數據進行更高級別的并發訪問。雖然一次只有一個線程(writer 線程)可以修改共享數據,但在許多情況下,任何數量的線程可以同時讀取共享數據(reader 線程),讀-寫鎖利用了這一點。從理論上講,與互斥鎖相比,使用讀-寫鎖所允許的并發性增強將帶來更大的性能提高。在實踐中,只有在多處理器上并且只在訪問模式適用于共享數據時,才能完全實現并發性增強。
    ???與互斥鎖相比,使用讀-寫鎖能否提升性能則取決于讀寫操作期間讀取數據相對于修改數據的頻率,以及數據的爭用——即在同一時間試圖對該數據執行讀取或寫入操作的線程數。例如,某個最初用數據填充并且之后不經常對其進行修改的 collection,因為經常對其進行搜索(比如搜索某種目錄),所以這樣的 collection 是使用讀-寫鎖的理想候選者。但是,如果數據更新變得頻繁,數據在大部分時間都被獨占鎖,這時,就算存在并發性增強,也是微不足道的。更進一步地說,如果讀取操作所用時間太短,則讀-寫鎖實現(它本身就比互斥鎖復雜)的開銷將成為主要的執行成本,在許多讀-寫鎖實現仍然通過一小段代碼將所有線程序列化時更是如此。最終,只有通過分析和測量,才能確定應用程序是否適合使用讀-寫鎖。
    盡管讀-寫鎖的基本操作是直截了當的,但實現仍然必須作出許多決策,這些決策可能會影響給定應用程序中讀-寫鎖的效果。這些策略的例子包括:

    • 在 writer 釋放寫入鎖時,reader 和 writer 都處于等待狀態,在這時要確定是授予讀取鎖還是授予寫入鎖。Writer 優先比較普遍,因為預期寫入所需的時間較短并且不那么頻繁。Reader 優先不太普遍,因為如果 reader 正如預期的那樣頻繁和持久,那么它將導致對于寫入操作來說較長的時延。公平或者“按次序”實現也是有可能的。
    • 在 reader 處于活動狀態而 writer 處于等待狀態時,確定是否向請求讀取鎖的 reader 授予讀取鎖。Reader 優先會無限期地延遲 writer,而 writer 優先會減少可能的并發。
    • 確定是否重新進入鎖:可以使用帶有寫入鎖的線程重新獲取它嗎?可以在保持寫入鎖的同時獲取讀取鎖嗎?可以重新進入寫入鎖本身嗎?
    • 可以將寫入鎖在不允許其他 writer 干涉的情況下降級為讀取鎖嗎?可以優先于其他等待的 reader 或 writer 將讀取鎖升級為寫入鎖嗎?
    public interface ReadWriteLock {
     
     /** 返回用于讀的鎖
       */
        Lock readLock();
    
        /**返回用于寫的鎖
         */
        Lock writeLock();
    }
    
  • Condition接口
    ConditionObject 監視器方法(waitnotifynotifyAll)分解成截然不同的對象,以便通過將這些對象與任意 Lock 實現組合使用,為每個對象提供多個等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和語句的使用,Condition 替代了 Object 監視器方法的使用。

條件(也稱為條件隊列條件變量)為線程提供了一個含義,以便在某個狀態條件現在可能為 true 的另一個線程通知它之前,一直掛起該線程(即讓其“等待”)。因為訪問此共享狀態信息發生在不同的線程中,所以它必須受保護,因此要將某種形式的鎖與該條件相關聯。等待提供一個條件的主要屬性是:以原子方式 釋放相關的鎖,并掛起當前線程,就像 Object.wait 做的那樣。

Condition 實例實質上被綁定到一個鎖上。要為特定 Lock 實例獲得 Condition 實例,請使用其 newCondition() 方法。

作為一個示例,假定有一個綁定的緩沖區,它支持 puttake 方法。如果試圖在空的緩沖區上執行 take 操作,則在某一個項變得可用之前,線程將一直阻塞;如果試圖在滿的緩沖區上執行 put 操作,則在有空間變得可用之前,線程將一直阻塞。我們喜歡在單獨的等待 set 中保存 put 線程和 take 線程,這樣就可以在緩沖區中的項或空間變得可用時利用最佳規劃,一次只通知一個線程。可以使用兩個 Condition 實例來做到這一點。

class BoundedBuffer {
   **final Lock lock = new ReentrantLock();**
   final Condition notFull  = **lock.newCondition();** 
   final Condition notEmpty = **lock.newCondition();** 

   final Object[] items = new Object[100];
   int putptr, takeptr, count;

   public void put(Object x) throws InterruptedException {
     **lock.lock();
     try {**
       while (count == items.length) 
         **notFull.await();**
       items[putptr] = x; 
       if (++putptr == items.length) putptr = 0;
       ++count;
       **notEmpty.signal();**
     **} finally {
       lock.unlock();
     }**
   }

   public Object take() throws InterruptedException {
     **lock.lock();
     try {**
       while (count == 0) 
         **notEmpty.await();**
       Object x = items[takeptr]; 
       if (++takeptr == items.length) takeptr = 0;
       --count;
       **notFull.signal();**
       return x;
     **} finally {
       lock.unlock();
     }**
   } 
 }

ArrayBlockingQueue 類提供了這項功能,因此沒有理由去實現這個示例類。)

Condition 實現可以提供不同于 Object 監視器方法的行為和語義,比如受保證的通知排序,或者在執行通知時不需要保持一個鎖。如果某個實現提供了這樣特殊的語義,則該實現必須記錄這些語義。

注意,Condition 實例只是一些普通的對象,它們自身可以用作 synchronized 語句中的目標,并且可以調用自己的 waitnotification 監視器方法。獲取 Condition 實例的監視器鎖或者使用其監視器方法,與獲取和該 Condition 相關的 Lock 或使用其 waitingsignalling 方法沒有什么特定的關系。為了避免混淆,建議除了在其自身的實現中之外,切勿以這種方式使用 Condition 實例。

除非另行說明,否則為任何參數傳遞 null 值將導致拋出 NullPointerException

實現注意事項

在等待 Condition 時,允許發生“虛假喚醒”,這通常作為對基礎平臺語義的讓步。對于大多數應用程序,這帶來的實際影響很小,因為 Condition 應該總是在一個循環中被等待,并測試正被等待的狀態聲明。某個實現可以隨意移除可能的虛假喚醒,但建議應用程序程序員總是假定這些虛假喚醒可能發生,因此總是在一個循環中等待。

三種形式的條件等待(可中斷、不可中斷和超時)在一些平臺上的實現以及它們的性能特征可能會有所不同。尤其是它可能很難提供這些特性和維護特定語義,比如排序保證。更進一步地說,中斷線程實際掛起的能力在所有平臺上并不是總是可行的。

因此,并不要求某個實現為所有三種形式的等待定義完全相同的保證或語義,也不要求其支持中斷線程的實際掛起。

要求實現清楚地記錄每個等待方法提供的語義和保證,在某個實現不支持中斷線程的掛起時,它必須遵從此接口中定義的中斷語義。

由于中斷通常意味著取消,而又通常很少進行中斷檢查,因此實現可以先于普通方法的返回來對中斷進行響應。即使出現在另一個操作后的中斷可能會釋放線程鎖時也是如此。實現應記錄此行為。

public interface Condition {

    //   造成當前線程在接到信號或被[中斷](http://tool.oschina.net/uploads/apidocs/jdk-    zh/java/lang/Thread.html#interrupt())之前一直處于等待狀態。
   void await() throws InterruptedException;
      //   造成當前線程在接到信號之前一直處于等待狀態。
    void awaitUninterruptibly();
    // 造成當前線程在接到信號、被中斷或到達指定等待時間之前一直處于等待狀態。
    long awaitNanos(long nanosTimeout) throws InterruptedException;
        //造成當前線程在接到信號之前一直處于等待狀態。
    boolean await(long time, TimeUnit unit) throws InterruptedException;
        // 造成當前線程在接到信號、被中斷或到達指定最后期限之前一直處于等待狀態。
    boolean awaitUntil(Date deadline) throws InterruptedException;
        //喚醒一個等待在Condition上的線程
    void signal();
       //喚醒所有等待在這個Condition上的線程
    void signalAll();
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,327評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,996評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,316評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,406評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,128評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,524評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,576評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,759評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,310評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,065評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,249評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,821評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,479評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,909評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,140評論 1 290
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,984評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,228評論 2 375

推薦閱讀更多精彩內容