Java多線程:生產(chǎn)者與消費(fèi)者案例
前言
想象一下生活中哪些是和線程沾邊的?飯店炒菜就是一個(gè)很好的例子
首先客人要吃菜,前提是廚師要炒好,也就是說(shuō),廚師不炒好的話客人是沒(méi)有飯菜的。這時(shí)候,廚師就是一個(gè)線程,客人拿菜就是另一個(gè)線程。
工具
jdk13,IDEA2019.1.4
知識(shí)點(diǎn)
Thread、Runnable、synchronized、面向?qū)ο笾R(shí)(繼承、封裝、接口、方法重寫)、條件判斷以及線程的一些其他知識(shí)點(diǎn)
設(shè)計(jì)思路
首先要有兩個(gè)線程,也就是說(shuō)要兩個(gè)類,分別是Producer(生產(chǎn)者)和Consumer(消費(fèi)者)。
由于我們是模擬廚師與客人之間的互動(dòng),也就是說(shuō)還需要一個(gè)類來(lái)封裝信息:Message(類)。
然后,避免線程之間發(fā)生數(shù)據(jù)混亂的情況,肯定還需要使用synchronized來(lái)進(jìn)行線程同步。
具體步驟
首先我們來(lái)一份沒(méi)有用synchronized的代碼,先看看效果:
public class Message { private String title; private String content; Message(){ }; public Message(String title, String content) { this.title = title; this.content = content; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } } /* * 定義生產(chǎn)者類Producer * */ class Producer implements Runnable{ private Message msg=null; public Producer(Message msg) { this.msg = msg; } @Override public void run() { for (int i=0;i<=50;i++){ if (i%2==0){ this.msg.setTitle("喜歡夜雨嗎?"); try { Thread.sleep(100); }catch (InterruptedException e){ System.out.println(e); } this.msg.setContent("是的呢!"); }else { this.msg.setTitle("還不關(guān)注我嗎?"); try { Thread.sleep(100); }catch (InterruptedException e){ System.out.println(e); } this.msg.setContent("好的呢!"); } } } } /* * 定義消費(fèi)者類Consumer * */ class Consumer implements Runnable{ private Message msg=null; public Consumer(Message msg) { this.msg = msg; } @Override public void run() { for (int i=0;i<=50;i++){ try { Thread.sleep(100); }catch (InterruptedException e){ System.out.println(e); } System.out.println(this.msg.getTitle()+"--->"+this.msg.getContent()); } } } class TestDemo{ public static void main(String[] args) { Message msg=new Message(); new Thread(new Producer(msg)).start(); new Thread(new Consumer(msg)).start(); } }
看看運(yùn)行結(jié)果:
看仔細(xì)咯,發(fā)生了數(shù)據(jù)錯(cuò)亂啊,Title與Content沒(méi)有一一對(duì)應(yīng)欸。咋辦?
能咋辦,改代碼唄。
發(fā)生數(shù)據(jù)混亂的原因完全是因?yàn)?,生產(chǎn)者線程還沒(méi)生產(chǎn)的時(shí)候,消費(fèi)者就已經(jīng)消費(fèi)了(至于消費(fèi)的啥我也不知道,我也不敢問(wèn)?。K栽斐闪藬?shù)據(jù)混亂,不過(guò)我們上面說(shuō)了啊,要使用synchronized來(lái)讓線程同步一下。但是又會(huì)出問(wèn)題,我們接著往下看
class TestDemo{ public static void main(String[] args) { Message msg=new Message(); new Thread(new Producer(msg)).start(); new Thread(new Consumer(msg)).start(); } } class Message{ private String title,content; public synchronized void set(String title,String content){ this.title=title; this.content=content; } public synchronized void get(){ try { Thread.sleep(1000); }catch (InterruptedException e){ System.out.println(e); } System.out.println(this.title+"-->"+this.content); } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } } class Producer implements Runnable{ private Message msg=null; Producer(Message msg){ this.msg=msg; } @Override public void run() { for (int i=0;i<=50;i++){ if (i%2==0){ this.msg.set("喜歡夜雨嗎?","是的呢!"); }else { this.msg.set("還不關(guān)注嗎?","好的呢!"); } } } } class Consumer implements Runnable{ private Message msg=null; Consumer(Message msg){ this.msg=msg; } @Override public void run() { for (int i=0;i<=50;i++){ this.msg.get(); } } }
我又重新封裝了一些方法,然后運(yùn)行的時(shí)候,wc,數(shù)據(jù)倒是不混亂了,但是呢,數(shù)據(jù)重復(fù)了一大堆。
為啥會(huì)出現(xiàn)這個(gè)問(wèn)題呢?最后想了一下,會(huì)出現(xiàn)這種問(wèn)題的,就是因?yàn)榫€程的執(zhí)行順序的問(wèn)題。我們想要實(shí)現(xiàn)的效果是生產(chǎn)者線程執(zhí)行了之后,讓生產(chǎn)者線程等待,而后讓消費(fèi)者線程執(zhí)行,等待消費(fèi)者線程執(zhí)行完成之后就又讓生產(chǎn)者線程繼續(xù)執(zhí)行。后來(lái)我又查了查官方文檔,發(fā)現(xiàn)Object類中專門有三個(gè)方法是與線程相關(guān)的:
方法 | 描述 |
---|---|
public final void wait() throws InterruptedException | 線程的等待 |
public final void notify() | 喚醒第一個(gè)等待線程 |
public final void notifyAll() |
當(dāng)我看到這些方法的時(shí)候,心里愣了一下,這不就是我們想要的方法嗎,用wait()方法來(lái)讓生產(chǎn)者線程等待,然后運(yùn)行消費(fèi)者線程,等消費(fèi)者線程執(zhí)行完了之后又讓生產(chǎn)者線程去執(zhí)行。這其中我們用true和false來(lái)表示運(yùn)行開(kāi)始和運(yùn)行暫停。
最后我們來(lái)看看完成品的代碼:
class TestDemo{ public static void main(String[] args) { Message msg=new Message(); new Thread(new Producer(msg)).start(); new Thread(new Consumer(msg)).start(); } } class Message extends Exception{ private String title,content; private boolean flag=true; // true表示正在生產(chǎn),不要來(lái)取走,因?yàn)闆](méi)由讓消費(fèi)者區(qū)走的 // false表示可以取走,但是不能生產(chǎn) public synchronized void set(String title,String content){ if (this.flag==false){ try { super.wait(); }catch (InterruptedException e){ System.out.println(e); } } this.title=title; try { Thread.sleep(60); }catch (InterruptedException e){ System.out.println(e); } this.content=content; this.flag=true; // 生產(chǎn)完成,修改標(biāo)志位 super.notify(); // 喚醒等待線程 } public synchronized void get(){ if (flag==true) {// 已經(jīng)生產(chǎn)好了,等待取走 try { super.wait(); }catch (InterruptedException e){ System.out.println(e); } } try { Thread.sleep(60); }catch (InterruptedException e){ System.out.println(e); } System.out.println(this.title+"-->"+this.content); this.flag=true; super.notify(); } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } } class Producer implements Runnable{ private Message msg=null; Producer(Message msg){ this.msg=msg; } @Override public void run() { for (int i=0;i<=50;i++){ if (i%2==0){ this.msg.set("喜歡夜雨嗎?","是的呢!"); }else { this.msg.set("還不關(guān)注嗎?","好的呢!"); } } } } class Consumer implements Runnable{ private Message msg=null; Consumer(Message msg){ this.msg=msg; } @Override public void run() { for (int i=0;i<=50;i++){ this.msg.get(); } } }
運(yùn)行結(jié)果我就不貼了,你們自己去測(cè)試一下吧…
總結(jié)
這個(gè)案例完美的呈現(xiàn)了線程以及面向?qū)ο笾R(shí)的綜合運(yùn)用,具有很強(qiáng)的實(shí)際操作性
本篇文章就到這里了,希望能給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
Java程序初始化啟動(dòng)自動(dòng)執(zhí)行的三種方式
這篇文章主要介紹了Java程序初始化啟動(dòng)自動(dòng)執(zhí)行的三種方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01Spring Boot啟動(dòng)過(guò)程(六)之內(nèi)嵌Tomcat中StandardHost、StandardContext和Sta
這篇文章主要介紹了Spring Boot啟動(dòng)過(guò)程(六)之內(nèi)嵌Tomcat中StandardHost、StandardContext和StandardWrapper的啟動(dòng)教程詳解,需要的朋友可以參考下2017-04-04Java 使用JdbcTemplate 中的queryForList發(fā)生錯(cuò)誤解決辦法
這篇文章主要介紹了Java 使用JdbcTemplate 中的queryForList發(fā)生錯(cuò)誤解決辦法的相關(guān)資料,需要的朋友可以參考下2017-07-07解決IDEA service層跳轉(zhuǎn)實(shí)現(xiàn)類的快捷圖標(biāo)消失問(wèn)題
這篇文章主要介紹了解決IDEA service層跳轉(zhuǎn)實(shí)現(xiàn)類的快捷圖標(biāo)消失問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02springboot+camunda實(shí)現(xiàn)工作流的流程分析
Camunda是基于Java語(yǔ)言,支持BPMN標(biāo)準(zhǔn)的工作流和流程自動(dòng)化框架,并且還支持CMMN規(guī)范,DMN規(guī)范,本文給大家介紹springboot+camunda實(shí)現(xiàn)工作流的流程分析,感興趣的朋友一起看看吧2021-12-12