線程間通信 概念:多個線程在處理同一個資源,但是處理的動作(線程的任務)卻不相同。 比如:線程A用來生成包子的,線程B用來吃包子的,包子可以理解為同一資源,線程A與線程B處理的動作,一個 是生產,一個是消費,那麼線程A與線程B之間就存線上程通信問題。 為什麼要處理線程間通信: 多個線程併發執行時, ...
線程間通信
概念:多個線程在處理同一個資源,但是處理的動作(線程的任務)卻不相同。
比如:線程A用來生成包子的,線程B用來吃包子的,包子可以理解為同一資源,線程A與線程B處理的動作,一個 是生產,一個是消費,那麼線程A與線程B之間就存線上程通信問題。
為什麼要處理線程間通信:
多個線程併發執行時, 在預設情況下CPU是隨機切換線程的,當我們需要多個線程來共同完成一項任務時,我們都知道執行任務時一般都是按規律或者規則來執行,所以多線程執行任務也是一樣,需要規律來協調通信,以此來幫我們達到多線程共同操作一項任務(一份數據)。
如何保證線程間通信有效執行任務(利用資源):
多個線程在處理同一個資源,並且任務不同時,需要線程通信來幫助解決線程之間對同一個變數的使用或操作。 因為多個線程在操作同一份數據時, 要避免對同一共用變數的爭奪。因此我們需要通過一定的手段使各個線程能有效的利用資源。而這種手段即—— 等待喚醒機制
等待喚醒機制
什麼時等待喚醒機制
這是多個線程間的一種協作機制。談到線程我們經常想到的是線程間的競爭(race),比如去爭奪鎖,但這並不是故事的全部,線程間也會有協作機制,就是在一個線程進行了規定操作後,就進入等待狀態(wait()), 等待其他線程執行完他們的指定代碼過後再將其喚醒(notify());在有多個線程進行等待時, 如果需要,可以使用 notifyAll()來喚醒所有的等待線程
wait/notify 就是線程間的一種協作機制
等待喚醒中的方法
等待喚醒機制就是用於解決線程間通信的問題的,使用到的3個方法的含義如下
1. wait:線程不再活動,不再參與調度,進入 wait set 中,因此不會浪費 CPU 資源,也不會去競爭鎖了,這時的線程狀態即是WAITING。它還要等著別的線程執行一個特別的動作,也即是“通知(notify)”在這個對象上等待的線程從wait set 中釋放出來,重新進入到調度隊列(ready queue)中
2. notify:則選取所通知對象的 wait set 中的一個線程釋放;例如,餐館有空位置後,等候就餐最久的顧客最先入座。
3. notifyAll:則釋放所通知對象的 wait set 上的全部線程。
註意:哪怕只通知了一個等待的線程,被通知線程也不能立即恢復執行,因為它當初中斷的地方是在同步塊內,而此刻它已經不再持有鎖,所以需要再次嘗試去獲取鎖(很可能面臨其它線程的競爭),獲取鎖成功後才能在當初調用 wait 方法之後的地方恢復執行
總結:如果能獲取鎖,線程就從 WAITING 狀態變成 RUNNABLE 狀態; 否則,從 wait set 出來,又進入 entry set,線程就從 WAITING 狀態又變成 BLOCKED 狀態
調用wait和notify方法需要註意的細節
1. wait方法與notify方法必須要由同一個鎖對象調用。因為:對應的鎖對象可以通過notify喚醒使用同一個鎖對 象調用的wait方法後的線程。
2. wait方法與notify方法是屬於Object類的方法的。因為:鎖對象可以是任意對象,而任意對象的所屬類都是繼 承了Object類的。
3. wait方法與notify方法必須要在同步代碼塊或者是同步函數中使用。因為:必須要通過鎖對象調用這2個方法。
等待喚醒機制是“生產者與消費者之間的關係”
就拿生產包子消費包子來說等待喚醒機制如何有效利用資源:
包子鋪線程生產包子,吃貨線程消費包子。當包子沒有時(包子狀態為false),吃貨線程等待,包子鋪線程生產包子 (即包子狀態為true),並通知吃貨線程(解除吃貨的等待狀態),因為已經有包子了,那麼包子鋪線程進入等待狀態。 接下來,吃貨線程能否進一步執行則取決於鎖的獲取情況。如果吃貨獲取到鎖,那麼就執行吃包子動作,包子吃完(包 子狀態為false),並通知包子鋪線程(解除包子鋪的等待狀態),吃貨線程進入等待。包子鋪線程能否進一步執行則取決於鎖的獲取情況
包子類
1 package demosummary.waitingandwake; 2 3 /** 4 包子鋪線程生產包子,吃貨線程消費包子。當包子沒有時(包子狀態為false),吃貨線程等待, 5 包子鋪線程生產包子 (即包子狀態為true) 6 並通知吃貨線程(解除吃貨的等待狀態),因為已經有包子了,那麼包子鋪線程進入等待狀態。 7 接下來,吃貨線程能否進一步執行則取決於鎖的獲取情況。 8 如果吃貨獲取到鎖,那麼就執行吃包子動作,包子吃完(包 子狀態為false),並通知包子鋪線程(解除包子鋪的等待狀態), 9 吃貨線程進入等待。包子鋪線程能否進一步執行則取決於鎖的獲取情況 10 */ 11 public class BaoZi { 12 private String pi; 13 private String xian; 14 boolean flag = false; 15 16 public BaoZi() { 17 } 18 19 public BaoZi(String pi, String xian, boolean flag) { 20 this.pi = pi; 21 this.xian = xian; 22 this.flag = flag; 23 } 24 25 public String getPi() { 26 return pi; 27 } 28 29 public void setPi(String pi) { 30 this.pi = pi; 31 } 32 33 public String getXian() { 34 return xian; 35 } 36 37 public void setXian(String xian) { 38 this.xian = xian; 39 } 40 41 public boolean isFlag() { 42 return flag; 43 } 44 45 public void setFlag(boolean flag) { 46 this.flag = flag; 47 } 48 49 @Override 50 public String toString() { 51 return "BaoZi{" + 52 "pi='" + pi + '\'' + 53 ", xian='" + xian + '\'' + 54 ", flag=" + flag + 55 '}'; 56 } 57 }
包子鋪類
1 package demosummary.waitingandwake; 2 3 public class BaoZiPu extends Thread{ 4 private BaoZi bz; 5 6 public BaoZiPu(String name, BaoZi bz) { 7 super(name); 8 this.bz = bz; 9 } 10 11 @Override 12 public void run() { 13 //定義一個變數來判斷做什麼皮和餡的包子 14 int count = 0; 15 while (true) { 16 synchronized (bz) { 17 if (bz.flag == true) {//包子存在 18 try { 19 bz.wait();//進入等待狀態,既不需要做包子 20 } catch (InterruptedException e) { 21 e.printStackTrace(); 22 } 23 } 24 //沒有包子,包子鋪開始做包子 25 System.out.println("包子鋪開始做包子"); 26 //判斷做什麼包子 27 if (count != 0) { 28 //做冰皮蛋黃包子 29 bz.setPi("冰皮"); 30 bz.setXian("蛋黃"); 31 } else { 32 //做薄皮豆沙餡 33 bz.setPi("薄皮"); 34 bz.setXian("豆沙"); 35 } 36 count++; 37 //改變包子的狀態為有包子 38 bz.flag = true; 39 System.out.println("包子做好了:"+bz.getPi()+bz.getXian()+"包子"); 40 System.out.println("請等待的顧客可以來拿包子了"); 41 bz.notify(); 42 } 43 } 44 } 45 }
顧客類
1 package demosummary.waitingandwake; 2 3 public class GuKe extends Thread{ 4 private BaoZi bz; 5 6 public GuKe(String name, BaoZi bz) { 7 super(name); 8 this.bz = bz; 9 } 10 11 @Override 12 public void run() { 13 while (true) { 14 synchronized (bz) { 15 if (bz.flag == false) { 16 try { 17 bz.wait(); 18 } catch (InterruptedException e) { 19 e.printStackTrace(); 20 } 21 } 22 23 System.out.println("顧客已拿到" + bz.getPi() + bz.getXian()+"包子"); 24 bz.flag = false; 25 bz.notify(); 26 } 27 } 28 } 29 }
測試類
1 package demosummary.waitingandwake; 2 3 public class Test { 4 public static void main(String[] args) { 5 //創建包子、包子鋪、顧客對象 6 BaoZi baoZi = new BaoZi(); 7 BaoZiPu baoZiPu = new BaoZiPu("包子鋪", baoZi); 8 GuKe guKe = new GuKe("顧客", baoZi); 9 //調用包子鋪和顧客線程 10 baoZiPu.start(); 11 guKe.start(); 12 } 13 }