Java多線程編程核心技術-第4章-Lock的使用-讀書筆記

第 4 章 Lock 的使用

本章主要內容

ReentrantLocal 類的使用。
ReentrantReadWriteLock 類的使用。

4.1 使用 ReentrantLock 類

  在 Java 多線程中,可以使用 synchronized 關鍵字來實現線程之間同步互斥,但在 JDK 1.5 中新增加了 ReentrantLock 類也是達到同樣的效果,並且在擴展功能上也更加強大,比如具有嗅探鎖定、多路分支通知等功能,而且在使用上也比 synchronized 更加的靈活。

4.1.1 使用 ReentrantLock 實現同步:測試 1

  調用 ReentrantLock 對象的 lock() 方法獲得鎖,調用 unlock() 方法釋放鎖。

  當前線程打印完畢之後將鎖進行釋放,其他線程纔可以繼續打印。線程打印的數據是分組打印(一個線程打印的數據在一塊),因爲當前線程已經持有鎖,但線程之間打印的順序是隨機的。

4.1.2 使用 ReentrantLock 實現同步:測試 2

  調用 lock.lock() 代碼的線程就持有了“對象監視器”,其他線程只有等待鎖被釋放時再次爭搶。效果和使用 synchronized 關鍵字一樣,線程之間還是順序執行的。

4.1.3 使用 Condition 實現等待 / 通知:錯誤用法與解決

  關鍵字 synchronized 與 wait() 和 notify()/notifyAll() 方法相結合可以實現等待 / 通知模式,類 ReentrantLock 也可以實現同樣的功能,但需要藉助於 Condition 對象。Condition 類是 JDK5 中出現的技術,使用它有更好的靈活性,比如可以實現多路通知功能,也就是在一個 Lock 對象裏面可以創建多個 Condition(即對象監視器)實例,線程對象可以註冊在指定的 Condition 中,從而可以有選擇性地進行線程通知,在調度線程上更加靈活。

  在使用 notify()/notifyAll() 方法進行通知時,被通知的線程卻是由 JVM 隨機選擇的。但是用 ReentrantLock 結合 Condition 類是可以實現“選擇性通知”,這個功能是非常重要的,而且在 Condition 類中是默認提供的。

  而 synchronized 就相當於整個 Lock 對象中只有一個單一的 Condition 對象,所有的線程都註冊在它一個對象的身上。線程開始 notifyAll() 時,需要通知所有的 WAITING 線程,沒有選擇權,會出現相當大的效率問題。

  在 condition.wait() 方法調用之前調用 lock.lock() 代碼獲得同步監視器,不然就會報 java.lang.IllegalMonitorStateException 異常。

  調用 Condition 對象的 await() 方法,使當前執行任務的線程進入了等待 WAITING 狀態。

4.1.4 正確使用 Condition 實現等待 / 通知

  Object 類中的 wait() 方法相當於 Condition 類中的 await() 方法。

  Object 類中的 wait(long timeout) 方法相當於 Condition 類中的 await(long time,TimeUnit unit) 方法。

  Object 類中的 notify() 方法相當於 Condition 類中的 signal() 方法。

  Object 類中的 notifyAll() 方法相當於 Ccondition 類中的 signalAll() 方法。

4.1.5 使用多個 Condition 實現通知部分線程:錯誤用法

  使用一個 Condition 對象,多個方法等待,當調用 signalAll() 方法喚醒等待線程時,所有的等待線程都會被喚醒。

  如果想要單獨喚醒部分線程該怎麼處理呢?這時就有必要使用多個 Condition 對象了,也就是 Condition 對象可以喚醒部分指定線程,有助於提升程序運行的效率。可以先對線程進行分組,然後再喚醒指定組中的線程。

4.1.6 使用多個 Condition 實現通知部分線程:正確用法

  使用多個 Condition 對象,可以實現通知部分線程。

  使用 ReentrantLock 對象可以喚醒指定種類的線程,這是控制部分線程行爲的方便方式。

4.1.7 實現生產者 / 消費者模式:一對一交替打印

  通過使用 Condition 對象,成功實現交替打印的效果。

4.1.8 實現生產者 / 消費者模式:多對多交替打印

  使用多個線程來交替打印,如果使用 signal() 方法就會出現假死,要使用 signalAll() 方法來解決。

4.1.9 公平鎖與非公平鎖

  公平與非公平鎖:鎖 Lock 分爲 “公平鎖” 和 “非公平鎖”,公平鎖表示線程獲得鎖的順序是按照線程加鎖的順序來分配的,即先來先得的 FIO 先進先出順序。而非公平鎖就是一種獲取鎖的搶佔機制,是隨機獲得鎖的,和公平鎖不一樣的就是先來的不一定先得到鎖,這個方式可能造成某些線程一直拿不到鎖,結果也就是不公平的了。

  new ReentrantLock(boolean isFair) 傳遞參數 isFair 表示是否是公平鎖。公平鎖的結果是基本程序有序的狀態,不保證完全有序。

4.1.10 方法 getHoldCount()、getQueueLength() 和 getWaitQueueLength() 的測試

  1)方法 int getHoldCount() 的作用是查詢當前線程保持此鎖定的個數,也就是調用 lock() 方法的次數。

  2)方法 int getQueueLength() 的作用是返回正等待獲取此鎖定的線程估計數。比如有 5 個線程,1 個線程首先執行 await() 方法,那麼在調用 getQueueLength() 方法後返回值是 4,說明有 4 個線程同時再等待 lock 的釋放。

  3)方法 int getWaitQueueLength(Condition condition) 的作用是返回等待與此鎖定相關的給定條件 Condition 的線程估計數,比如 5 個線程,每個線程都執行了同一個 condition 對象的 await() 方法,則調用 getWaitQueueLength(Condition condition) 方法時返回的 int 值是 5 。

4.1.11 方法 hasQueuedThread()、hasQueuedThreads() 和 hasWaiters() 的測試

  1)方法 boolean hasQueuedThread(Thread thread) 的作用是查詢指定的線程是否正在等待獲取此鎖定。

  方法 boolean hasQueuedThreads() 的作用是查詢是否有線程正在等待獲取此鎖定。

  2)方法 boolean hasWaiters(Condition condition) 的作用是查詢是否有線程正在等待與此鎖定有關的 condition 條件。

4.1.12 方法 isFair()、isHeldByCurrentThread() 和 isLocked() 的測試

  1)方法 boolean isFair() 的作用是判斷是不是公平鎖。

  在默認的情況下,ReentrantLock 類使用的是非公平鎖。

  2)方法 boolean isHeldByCurrentThread() 的作用是查詢當前線程是否保持此鎖定。

  3)方法 boolean isLocked() 的作用是查詢此鎖定是否由任意線程保持。

4.1.13 方法 lockInterruptibly()、tryLock() 和 tryLock(long timeout,TimeUnit unit) 的測試

  1)方法 void lockInterruptibly() 的作用是:如果當前線程未被中斷,則獲取鎖定,如果已經被中斷則出現異常。

  當調用 lock.lockInterruptibly() 方法時,調用 thread.interrupt() 方法是會報異常。

  2)方法 boolean tryLock() 的作用是,僅在調用時鎖定未被另一個線程保持的情況下,才獲得該鎖定。

  3)方法 boolean tryLock(long timeout,TimeUnit unit) 的作用是,如果鎖定在給定等待時間內沒有被另一個線程保持,且當前線程未被中斷,則獲取該鎖定。

4.1.14 方法 awitUniterruptibly() 的使用

  當調用了thread.interrupt()方法之後,condition.await() 方法會報異常,但是調用 awaitUniterruptibly() 方法不會報異常。

4.1.15 方法 awaitUntil() 的使用

  調用 awaitUntil(long time) 是等待 time 時間後自動喚醒。

4.1.16 使用 Condition 實現順序執行

  使用 Condition 對象可以對線程執行的業務進行排序規劃。

  使用 Condition 對線程進行分類,然後進行分類喚醒,從而實現排序規劃。

4.2 使用 ReentrantReadWriteLock 類

  類 ReentrantLock 具有完全互斥排他的效果,即同一時間只有一個線程在執行 ReentrantLock.lock() 方法後面的任務。這樣做雖然保證了實例變量的線程安全性,但效率卻是非常低下的。所以在 JDK 中提供了一種讀寫鎖 ReentrantReadWriteLock 類,使用它可以加快運行效率,在某些不需要操作實例變量的方法中,完全可以使用讀寫鎖 ReentrantReadWriteLock 來提升鈣方法的代碼運行速度。

  讀寫鎖表示也有兩個鎖,一個是讀操作相關的鎖,也稱爲共享鎖;另一個是寫操作相關的鎖,也叫排他鎖。也就是多個讀鎖之間不互斥,讀鎖與寫鎖互斥,寫鎖與寫鎖互斥。在沒有線程 Thread 進行寫入操作時,進行讀取操作的多個 Thread 都可以獲取讀鎖,而進入寫入操作的 Thread 只有在獲取寫鎖後才能進行寫入操作。即多個 Thread 可以同時進行讀取操作,但是同一時刻只允許一個 Thread 進行寫入操作。

4.2.1 類 ReentrantReadWriteLock 的使用:讀讀共享

  使用 lock.readLock() 讀鎖可以提高程序運行效率,允許多個線程同時執行 lock() 方法後面的代碼。兩個線程幾乎同時進入 lock() 方法後面的代碼。

4.2.2 類 ReentrantReadWriteLock 的使用:寫寫互斥

  使用寫鎖代碼 lock.writeLock() 的效果就是同一時間只允許一個線程執行 lock() 方法後面的代碼。

4.2.3 類 ReentrantReadWriteLock 的使用:讀寫互斥

  “讀寫”操作是互斥的。

4.2.4 類 ReentrantReadWriteLock 的使用:寫讀互斥

  “寫讀”操作也是互斥的。即只要出現 “寫操作” 的過程,就是互斥的。

  “讀寫”、“寫讀” 和 “寫寫” 都是互斥的;而 “讀讀” 是異步的,非互斥的。

4.3 本章總結

  在學習併發時,Lock 是 synchronized 關鍵字的進階,掌握 Lock 有助於學習併發包中源代碼的實現原理,在併發包中大量的類使用了 Lock 接口作爲同步的處理方式。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章