Java執行緒通信
螣蛇乘霧,終為土灰,
多個執行緒協同作業完成某個任務時就會涉及到執行緒間通信問題,如何使各個執行緒之間同時執行,順序執行、交叉執行等,
一、執行緒同時執行
創建兩個執行緒a和b,兩個執行緒內呼叫同一個列印 1-3 三個數字的方法,
1 package tjt; 2 3 import java.time.LocalDate; 4 5 public class Test { 6 7 /** 8 * 創建兩個執行緒a和b,兩個執行緒內呼叫同一個列印 1-3 三個數字的方法, 9 */ 10 private static void situationOne() { 11 Thread a = new Thread(new Runnable() { 12 @Override 13 public void run() { 14 doSomething("a"); 15 } 16 }); 17 Thread b = new Thread(new Runnable() { 18 @Override 19 public void run() { 20 doSomething("b"); 21 } 22 }); 23 a.start(); 24 b.start(); 25 } 26 27 /** 28 * 依次列印 1, 2, 3 三個數字 29 * 30 * @param threadName 31 */ 32 private static void doSomething(String threadName) { 33 int i = 0; 34 while (i++ < 3) { 35 try { 36 Thread.sleep(200); 37 } catch (InterruptedException e) { 38 e.printStackTrace(); 39 } 40 System.out.println(LocalDate.now() + " Thread " + threadName + " is doing, printing: " + i); 41 } 42 } 43 44 public static void main(String[] args) { 45 situationOne(); 46 } 47 }View Code
多次運行發現a和b是同時列印的,無執行順序可言,

二、執行緒順序執行
創建兩個執行緒a和b,要求b 在 a 全部列印完后再開始列印,使用 thread.join() 方法,在子執行緒呼叫了join()方法后面的代碼,只有等到子執行緒結束了才能執行,即必須a執行完畢后才輪到b,
1 package tjt; 2 3 import java.time.LocalDate; 4 5 public class Test { 6 7 /** 8 * 創建兩個執行緒a和b,要求b 在 a 全部列印完后再開始列印,使用 thread.join() 方法, 9 * 保證執行緒a執行完畢后才輪到b 10 */ 11 private static void situationOne() { 12 Thread a = new Thread(new Runnable() { 13 @Override 14 public void run() { 15 doSomething("a"); 16 } 17 }); 18 Thread b = new Thread(new Runnable() { 19 @Override 20 public void run() { 21 try { 22 System.out.println("執行緒 b 正在通過thread.join()等待執行緒 a 執行完畢后再潤"); 23 // thread.join() 在子執行緒呼叫了join()方法后面的代碼,只有等到子執行緒結束了才能執行,即必須a執行完畢后才輪到b 24 a.join(); 25 } catch (InterruptedException e) { 26 e.printStackTrace(); 27 } 28 doSomething("b"); 29 } 30 }); 31 a.start(); 32 b.start(); 33 } 34 35 /** 36 * 依次列印 1, 2, 3 三個數字 37 * 38 * @param threadName 39 */ 40 private static void doSomething(String threadName) { 41 int i = 0; 42 while (i++ < 3) { 43 try { 44 Thread.sleep(200); 45 } catch (InterruptedException e) { 46 e.printStackTrace(); 47 } 48 System.out.println(LocalDate.now() + " Thread " + threadName + " is doing, printing: " + i); 49 } 50 } 51 52 public static void main(String[] args) { 53 situationOne(); 54 } 55 }View Code
無論運行多少次,都是執行緒a先執行完畢再到執行緒b,

三、執行緒順序交叉執行
創建兩個執行緒a和b,要求 a 在列印完 1 后,再讓 b 列印 1、2、 3,接著再回到 a 繼續列印 2、3,如此順序交叉執行僅靠 Thread.join() 是無法滿足需求的,需要更細粒度的鎖來控制執行順序,以及object.wait() 和 object.notify() 兩個方法來實作,
1 package tjt; 2 3 import java.time.LocalDate; 4 5 public class TestAgain { 6 7 private static void situationTwo() { 8 // a 和 b 的共享物件鎖 lock 9 Object lock = new Object(); 10 Thread a = new Thread(new Runnable() { 11 @Override 12 public void run() { 13 // 同步鎖 lock 14 synchronized (lock) { 15 // a 獲得鎖后執行 16 doSomething("a", 1); 17 try { 18 // 呼叫 lock.wait() 方法,交出鎖的控制權,進入 wait 狀態,等待notify喚醒 19 lock.wait(); 20 } catch (InterruptedException e) { 21 e.printStackTrace(); 22 } 23 doSomething("a", 2); 24 doSomething("a", 3); 25 } 26 } 27 }); 28 Thread b = new Thread(new Runnable() { 29 @Override 30 public void run() { 31 // 同步鎖 lock 32 synchronized (lock) { 33 // a 獲得鎖后執行 34 doSomething("b", 1); 35 doSomething("b", 2); 36 doSomething("b", 3); 37 // 呼叫 lock.notify() 方法,喚醒正在 wait 的執行緒 a 38 lock.notify(); 39 } 40 } 41 }); 42 a.start(); 43 b.start(); 44 } 45 46 /** 47 * 列印 48 * 49 * @param threadName 50 * @param num 51 */ 52 private static void doSomething(String threadName, int num) { 53 System.out.println(LocalDate.now() + " Thread " + threadName + " is doing, printing: " + num); 54 } 55 56 public static void main(String[] args) { 57 situationTwo(); 58 } 59 }View Code
無論運行多少次,都是執行緒a先執行列印1,然后執行緒b執行列印1、2、3,最后執行緒a執行列印2、3,

四、CountDownLatch
CountDownLatch 計數器適用于一個執行緒去等待多個執行緒的情況,例如A B C 三個執行緒同時運行,各自獨立運行完后通知執行緒 D 執行,就可以利用 CountdownLatch 來實作這類通信方式,
對比之前的join方法,thread.join()可以讓一個執行緒等另一個執行緒運行完畢后再繼續執行,其可以在 D 執行緒里依次 join A B C,但這樣 A B C 必須依次執行,無法實作ABC三者能同步運行,
1 package tjt; 2 3 import java.time.LocalDate; 4 import java.util.concurrent.CountDownLatch; 5 6 public class TestCountDownLatch { 7 8 /** 9 * countDownLatch 適用于一個執行緒去等待多個執行緒的情況 10 * 四個執行緒A、B、C、D, 11 * 其中 D 要等到 A B C 全執行完畢后才執行,且 A B C 是同步運行的,即ABC無順序執行 12 */ 13 private static void situationThree() { 14 // 初始計數值設定為3,即總共四個執行緒A、B、C、D 15 CountDownLatch latch = new CountDownLatch(3); 16 new Thread(new Runnable() { 17 @Override 18 public void run() { 19 System.out.println(LocalDate.now() + "執行緒 D 等待執行緒A B C 執行完畢后才可執行"); 20 try { 21 // await() 檢查計數器值是否為 0,若不為 0 則保持等待狀態 22 latch.await(); 23 // 其他執行緒 的 countDown() 方法把計數值變成 0 時,等待執行緒里的 countDownLatch.await() 立即退出,繼續執行下面的代碼 24 System.out.println(LocalDate.now() + "執行緒A B C 執行完畢,輪到執行緒D 執行了,當前latch:" + latch.getCount()); 25 } catch (InterruptedException e) { 26 e.printStackTrace(); 27 } 28 } 29 }).start(); 30 31 // 回圈執行執行緒 A B C 32 for (char threadName = 'A'; threadName <= 'C'; threadName++) { 33 String name = String.valueOf(threadName); 34 new Thread(new Runnable() { 35 @Override 36 public void run() { 37 System.out.println("執行緒 " + name + " is running"); 38 // countDown(),將倒計數器減 1, 計數器被減至 0 時立即觸發D 的 await() 39 try { 40 Thread.sleep(200); 41 } catch (InterruptedException e) { 42 e.printStackTrace(); 43 } 44 System.out.println("執行緒 " + name + " 執行完畢計數器"); 45 latch.countDown(); 46 } 47 }).start(); 48 } 49 } 50 51 public static void main(String[] args) { 52 situationThree(); 53 } 54 55 }View Code

五、CyclicBarrier
實作執行緒間互相等待,可以利用 CyclicBarrier 柵欄,CountDownLatch 可以用來倒計數,但當計數完畢,只有一個執行緒的 await() 會得到回應,無法讓多個執行緒同時觸發,如要求執行緒 A B C 各自開始準備,直到三者都準備完畢再同時運行其就無法滿足需求,而用CyclicBarrier則完全OK,

螣蛇乘霧
終為土灰
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/450355.html
標籤:Java
