您好,登錄后才能下訂單哦!
上篇樓主說明了多線程中死鎖產(chǎn)生的原因并拋出問題——死鎖的解放方案,那么在本篇文章,樓主將引用一個(gè)KFC生產(chǎn)漢堡,顧客購買漢堡的過程來說明死鎖解決方案及多線程的等待喚醒機(jī)制。
簡單地用一幅圖來說明KFC生產(chǎn)漢堡,顧客來消費(fèi)的過程:
場景分析:
資源類:Hamburger
設(shè)置漢堡數(shù)據(jù):SetThread(生產(chǎn)者)
獲取漢堡數(shù)據(jù):GetThread(消費(fèi)者)
測試類:HamburgerTest
不同種類的線程(生產(chǎn)者、消費(fèi)者)針對同一資源(漢堡)的操作
當(dāng)漢堡有存貨的時(shí)候,漢堡師傅不再生產(chǎn),顧客可消費(fèi);反之,漢堡師傅生產(chǎn),顧客不可消費(fèi)
是否有線程安全問題?當(dāng)然。樓主在《線程安全問題》那篇文章給出了判定方式,在該場景全部滿足。
代碼構(gòu)建:類里面的i屬性是樓主為了效果好一些特意加的,與本文要說明的問題無關(guān);
首先是資源類Hamburger.java,樓主這里為了模擬只簡單的構(gòu)造了3個(gè)字段,其中flag用來表示資源是否有數(shù)據(jù)。
1 package com.jon.hamburger; 2 3 public class Hamburger { 4 private String name;//漢堡名稱 5 private double price;//漢堡價(jià)格 6 private boolean flag;//漢堡是否有數(shù)據(jù)的標(biāo)志,默認(rèn)為false,表示沒有數(shù)據(jù) 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 }
接著是生產(chǎn)者SetThread.java與GetThread.java,都需要實(shí)現(xiàn)Runnable接口。場景分析中的第7點(diǎn)已經(jīng)說明,場景存在線程安全的問題,樓主在前篇文章已經(jīng)說明,線程安全的問題可以通過加鎖來進(jìn)行解決,但是這里涉及到不同種類的線程,所以必須要滿足2點(diǎn):
不同種類的線程都要加鎖
不同種類的線程加的鎖必須是同一把
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 @Override11 public void run() {12 while (true) {//為了數(shù)據(jù)效果好一些,樓主加入了判斷13 synchronized (hamburger) {14 if(this.hamburger.isFlag()){//如果有存貨15 try {16 hamburger.wait();//線程等待17 } catch (InterruptedException e) { 18 e.printStackTrace();19 }20 }21 //如果沒有存貨,這模擬生產(chǎn)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);//生產(chǎn)完成后更改標(biāo)志30 hamburger.notify();//喚醒當(dāng)前等待的線程31 i++;//只為數(shù)據(jù)效果好一些,無實(shí)際意義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 * 為了讓同步鎖使用同一個(gè)對象鎖,這里通過構(gòu)造方法進(jìn)行傳遞 8 * @param hamburger 9 */10 public GetThread(Hamburger hamburger){11 this.hamburger = hamburger;12 }13 @Override14 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 //如果有數(shù)據(jù)則進(jìn)行輸出25 System.out.println(this.hamburger.getName()+"-----"+this.hamburger.getPrice());26 this.hamburger.setFlag(false);//更改標(biāo)志27 hamburger.notify();//喚醒線程28 } 29 } 30 31 }32 33 }
可以看到兩個(gè)線程類的run方法中都使用了sysnchronized進(jìn)行了加鎖,并使用同一個(gè)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);//通過構(gòu)造方法傳入共享資源數(shù)據(jù)hamburger12 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 }
測試類中,我們通過構(gòu)造方法給SetThread和GetThread傳入了同一個(gè)對象,以保證鎖對象為同一把。
輸出結(jié)果,線程間不相互影響,同時(shí)都無NULL------0.0的情況輸出:
1 俊鍋的漢堡-----25.02 大俊鍋的漢堡-----26.03 俊鍋的漢堡-----25.04 大俊鍋的漢堡-----26.05 俊鍋的漢堡-----25.06 大俊鍋的漢堡-----26.07 俊鍋的漢堡-----25.08 大俊鍋的漢堡-----26.0
代碼分析:
我們假設(shè)線程t2先搶到CPU的執(zhí)行權(quán),那么程序執(zhí)行流程可用下圖表示:
根據(jù)程序代碼分析也可見,由于線程之間相互等待產(chǎn)生的死鎖問題也得以解決,解決方案就是通過喚醒。另外,文本樓主還使用了另一種方式,思路也差不多,示例代碼與本文的示例代碼放在一起,已上傳到GitHub。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。