您的位置:首頁>正文

多執行緒等待喚醒機制之生產消費者模式

上篇樓主說明了多執行緒中鎖死產生的原因並拋出問題——鎖死的解放方案, 那麼在本篇文章, 樓主將引用一個KFC生產漢堡, 顧客購買漢堡的過程來說明鎖死解決方案及多執行緒的等待喚醒機制。

簡單地用一幅圖來說明KFC生產漢堡, 顧客來消費的過程:

場景分析:

資源類:Hamburger 設置漢堡數據:SetThread(生產者)獲取漢堡資料:GetThread(消費者)測試類:HamburgerTest不同種類的執行緒(生產者、消費者)針對同一資源(漢堡)的操作當漢堡有存貨的時候, 漢堡師傅不再生產, 顧客可消費;反之, 漢堡師傅生產, 顧客不可消費是否有執行緒安全問題?當然。 樓主在《執行緒安全問題》那篇文章給出了判定方式, 在該場景全部滿足。

代碼構建:類裡面的i屬性是樓主為了效果好一些特意加的, 與本文要說明的問題無關;

首先是資源類Hamburger.java, 樓主這裡為了模擬只簡單的構造了3個欄位, 其中flag用來表示資源是否有資料。

1 package com.jon.hamburger; 2 3 public class Hamburger { 4 private String name;//漢堡名稱 5 private double price;//漢堡價格 6 private boolean flag;//漢堡是否有資料的標誌, 預設為false, 表示沒有資料 7 public String getName { 8 return name; 9 } 10 public void setName(String name) { 11 this.name = name; 12 } 13 public double getPrice { 14 return price; 15 } 16 public void setPrice(double price) { 17 this.price = price; 18 } 19 public boolean isFlag { 20 return flag; 21 } 22 public void setFlag(boolean flag) { 23 this.flag = flag; 24 } 25 26 }

接著是生產者SetThread.java與GetThread.java, 都需要實現Runnable介面。 場景分析中的第7點已經說明, 場景存在執行緒安全的問題, 樓主在前篇文章已經說明, 執行緒安全的問題可以通過加鎖來進行解決, 但是這裡涉及到不同種類的執行緒, 所以必須要滿足2點:

不同種類的執行緒都要加鎖不同種類的執行緒加的鎖必須是同一把

SetThread.java

1 package com.jon.hamburger; 2 3 public class SetThread implements Runnable { 4 private Hamburger hamburger; 5 private int i; 6 7 public SetThread(Hamburger hamburger) { 8 this.hamburger = hamburger; 9 } 10 @Override 11 public void run { 12 while (true) {//為了資料效果好一些, 樓主加入了判斷 13 synchronized (hamburger) { 14 if(this.hamburger.isFlag){//如果有存貨 15 try { 16 hamburger.wait;//執行緒等待 17 } catch (InterruptedException e) { 18 e.printStackTrace; 19 } 20 } 21 //如果沒有存貨, 這模擬生產 22 if (i % 2 == 0) { 23 this.hamburger.setPrice(25.0); 24 this.hamburger.setName("俊鍋的漢堡"); 25 } else { 26 this.hamburger.setPrice(26.0); 27 this.hamburger.setName("大俊鍋的漢堡"); 28 } 29 this.hamburger.setFlag(true);//生產完成後更改標誌 30 hamburger.notify;//喚醒當前等待的執行緒 31 i++;//只為資料效果好一些, 無實際意義 32 } 33 34 } 35 36 } 37 38 }

GetThread.java

1 package com.jon.hamburger; 2 3 public class GetThread implements Runnable { 4 5 private Hamburger hamburger; 6 /** 7 * 為了讓同步鎖使用同一個物件鎖, 這裡通過構造方法進行傳遞 8 * @param hamburger 9 */ 10 public GetThread(Hamburger hamburger){ 11 this.hamburger = hamburger; 12 } 13 @Override 14 public void run { 15 while(true){ 16 synchronized (hamburger) { 17 if(!this.hamburger.isFlag){//如果沒有存貨,
執行緒等待 18 try { 19 hamburger.wait; 20 } catch (InterruptedException e) { 21 e.printStackTrace; 22 } 23 } 24 //如果有資料則進行輸出 25 System.out.println(this.hamburger.getName+"-----"+this.hamburger.getPrice); 26 this.hamburger.setFlag(false);//更改標誌 27 hamburger.notify;//喚醒執行緒 28 } 29 } 30 31 } 32 33 }

可以看到兩個執行緒類的run方法中都使用了sysnchronized進行了加鎖, 並使用同一個hamburger物件鎖。

再看測試類HamburgerTest.java及輸出:

1 package com.jon.hamburger; 2 3 4 5 public class HamburgerTest { 6 7 8 public static void main(String[] args) { 9 Hamburger hamburger = new Hamburger; 10 11 SetThread st = new SetThread(hamburger);//通過構造方法傳入共用資源資料hamburger 12 GetThread gt = new GetThread(hamburger); 13 14 Thread td1 = new Thread(st); 15 Thread td2 = new Thread(gt); 16 17 td1.start; 18 td2.start; 19 20 } 21 22 }

測試類中, 我們通過構造方法給SetThread和GetThread傳入了同一個物件, 以保證鎖物件為同一把。

輸出結果, 執行緒間不相互影響, 同時都無NULL------0.0的情況輸出:

1 俊鍋的漢堡-----25.0 2 大俊鍋的漢堡-----26.0 3 俊鍋的漢堡-----25.0 4 大俊鍋的漢堡-----26.0 5 俊鍋的漢堡-----25.0 6 大俊鍋的漢堡-----26.0 7 俊鍋的漢堡-----25.0 8 大俊鍋的漢堡-----26.0

代碼分析:

我們假設執行緒t2先搶到CPU的執行權, 那麼程式執行流程可用下圖表示:

根據程式碼分析也可見, 由於執行緒之間相互等待產生的鎖死問題也得以解決, 解決方案就是通過喚醒。 另外, 文本樓主還使用了另一種方式, 思路也差不多, 示例代碼與本文的示例代碼放在一起。

本文示例代碼地址:https://github.com/LJunChina/JavaResource中的Hamburger

同類文章
Next Article
喜欢就按个赞吧!!!
点击关闭提示