1. 程式人生 > >juc包:使用 juc 包下的顯式 Lock 實現執行緒間通訊

juc包:使用 juc 包下的顯式 Lock 實現執行緒間通訊


# 一、前置知識
執行緒間通訊三要素: 多執行緒+**判斷**+操作+**通知**+資源類。 上面的五個要素,其他三個要素就是普通的多執行緒程式問題,那麼通訊就需要執行緒間的互相通知,往往伴隨著何時通訊的判斷邏輯。 在 java 的 Object 類裡就提供了對應的方法來進行通知,同樣的,保證安全的判斷採用隱式的物件鎖,也就是 synchronized 關鍵字實現。這塊內容在: [java多執行緒:執行緒間通訊——生產者消費者模型](https://www.cnblogs.com/lifegoeson/p/13528098.html) 已經寫過。
# 二、使用 Lock 實現執行緒間通訊
那麼,我們知道 juc 包裡提供了顯式的鎖,即 Lock 介面的各種實現類,如果想用顯式的鎖來實現執行緒間通訊問題,喚醒方法就要使用**對應的 Conditon 類的 await 和 signalAll 方法**。(這兩個方法名字也能看得出來,對應 Object 類的 wait 和 notifyAll) Condition 物件的獲取可以通過具體的 Lock 實現類的物件的 newCondition 方法獲得。 ``` public class Communication2 { public static void main(String[] args) { Container container = new Container(); new Thread(()->
{ for (int i = 0; i < 10; i++){ container.increment(); } },"生產者1").start(); new Thread(()->{ for (int i = 0; i < 10; i++){ container.decrenment(); } },"消費者1").start(); new Thread(()->{ for (int i = 0; i < 10; i++){ container.increment(); } },"生產者2").start(); new Thread(()->{ for (int i = 0; i < 10; i++){ container.decrenment(); } },"消費者2").start(); new Thread(()->{ for (int i = 0; i < 10; i++){ container.increment(); } },"生產者3").start(); new Thread(()->{ for (int i = 0; i < 10; i++){ container.decrenment(); } },"消費者3").start(); } } class Container{ private int count = 0; private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); public void increment(){ lock.lock(); try { while (count != 0){ condition.await(); } count++; System.out.println(Thread.currentThread().getName() + " "+count); condition.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void decrenment(){ lock.lock(); try{ while (count == 0){ condition.await(); } count--; System.out.println(Thread.currentThread().getName()+ " " + count); condition.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } } ``` 輸出也沒有任何問題。 這裡面我們模擬了 3 個生產者和 3 個消費者,進行對一個資源類,其實就是一個數字 count 的操作,並使用 Condition 類來進行喚醒操作。 從目前程式碼的用法來看, juc 包的 Lock 介面實現類和之前使用 synchronized + Object 類的執行緒通訊方法是一樣的,但是併發包的開發工具給了他**更多的靈活性**。靈活在哪?
# 三、喚醒特定執行緒
新技術解決了舊問題,這個方法解決的問題就是,在生產者消費者問題裡:有時候我們並不想喚醒所有的對面夥伴,而只想要喚醒特定的一部分,這時候該怎麼辦呢? 如果沒有顯式的 lock,我們的思路可能是: 1. 採用一個標誌物件,可以是一個數值或者別的; 2. 當通訊呼叫 signalAll 的時候,其他執行緒都去判斷這個標誌,從而決定自己應不應該工作。 這種實現是可行的,但是本質上其他執行緒都被喚醒,然後一直阻塞+判斷,其實還是在競爭。那麼 Condition 類其實就已經提供了對應的方法,來完成這樣的操作: 我們看這樣一個需求: * 同樣是多執行緒操作、需要通訊。但是我們要**指定各個執行緒交替的順序**,以及**指定喚醒的時候是指定哪個具體執行緒**,這樣就不會存在喚醒所有執行緒然後他們之間互相競爭了。 * 具體說是:**AA 列印 5 次,BB 列印 10 次, CC 列印 15次,然後接著從 AA 開始一輪三個交替**。 程式碼如下: ``` public class TakeTurnPrint { public static void main(String[] args) { ShareResource resource = new ShareResource(); new Thread(()->
{resource.printA();},"A").start(); new Thread(()->{resource.printB();},"B").start(); new Thread(()->{resource.printC();},"C").start(); } } /** * 資源 */ class ShareResource{ private int signal = 0;//0-A,1-B,2-C private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); private Condition condition1 = lock.newCondition(); private Condition condition2 = lock.newCondition(); public void printA(){ lock.lock(); try{ while (signal != 0){ condition.await();//精準 } for (int i = 0; i < 5; i++){ System.out.println(Thread.currentThread().getName() + " " + i); } signal = 1;//精準 condition1.signal();//精準指定下一個 }catch (InterruptedException e) { e.printStackTrace(); }finally { lock.unlock(); } } public void printB(){ lock.lock(); try{ while (signal != 1){ condition1.await();//精準 } for (int i = 0; i < 10; i++){ System.out.println(Thread.currentThread().getName() + " " + i); } signal = 2;//精準 condition2.signal();//精準指定下一個 }catch (InterruptedException e) { e.printStackTrace(); }finally { lock.unlock(); } } public void printC(){ lock.lock(); try{ while (signal != 2){ condition2.await();//精準 } for (int i = 0; i < 15; i++){ System.out.println(Thread.currentThread().getName() + " " + i); } signal = 0;//精準 condition.signal();//精準指定下一個 }catch (InterruptedException e) { e.printStackTrace(); }finally { lock.unlock(); } } } ``` 其中,使用三個 Condition 物件,用一個 signal 的不同值,來通知不同的