菜雞的Java筆記 生產者與消費者
生產者與消費者
程式碼要求知道做什麼用即可
執行緒間的通訊問題以及 Object 類的支援
基礎模型
現在希望實現一種資料的生產和取出的操作形式,即:有兩個甚至更多的執行緒物件,這樣的執行緒分為生產者執行緒和消費者執行緒
那麼最理想的狀態是生產者每生產完一條完整的資料之後,消費者就要取走這個資料,並且進行輸出的列印
現在假設要輸出的資訊有這樣兩個:
title = 帥帥, content = 一個學生;
title = 可愛的小動物, content = 小貓咪;
範例:程式的初期實現
package cn.mysterious.actualcombat; class Info{ private String title; private String 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; } } class Producptor implements Runnable{ private Info info = null; public Producptor(Info info){ this.info = info; } @Override public void run() { // TODO Auto-generated method stub for(int i = 0; i < 50; i++) { if (i % 2 == 0) { // 偶數 this.info.setTitle("帥帥"); try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } this.info.setContent("一個學生"); }else { this.info.setTitle("可愛的動物"); try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } this.info.setContent("小貓咪"); } } } } class Consumer implements Runnable{ private Info info = null; public Consumer(Info info){ this.info = info; } @Override public void run() { // TODO Auto-generated method stub for (int i = 0; i < 50; i++) { try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(this.info.getTitle() + "-->" + this.info.getContent()); } } } public class ActualCombat{ public static void main(String[] args){ Info info = new Info(); Producptor p = new Producptor(info); Consumer c = new Consumer(info); new Thread(p).start(); new Thread(c).start(); } }
通過以上的執行可以發現有兩個問題:
第一:資料錯位了
第二:重複生產,重複取出
解決資料不同步問題
要想解決同步問題一定使用同步程式碼塊或者是同步方法,既然要同步,那麼肯定要將設定屬性和取得的屬性的內容都統一交給 Info 完成
範例:
package cn.mysterious.actualcombat; class Info{ private String title; private String content; public synchronized void set(String content, String title){ this.title = title; try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } this.content = content; } public synchronized void get(){ try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(this.title + "-->" +this.content ); } } class Producptor implements Runnable{ private Info info = null; public Producptor(Info info){ this.info = info; } @Override public void run() { // TODO Auto-generated method stub for (int i = 0; i < 50; i++) { if (i % 2 == 0) { // 偶數 this.info.set("帥帥","一個學生"); }else { this.info.set("可愛的動物","小貓咪"); } } } } class Consumer implements Runnable{ private Info info = null; public Consumer(Info info){ this.info = info; } @Override public void run() { // TODO Auto-generated method stub for (int i = 0; i < 50; i++) { this.info.get(); } } } public class ActualCombat{ public static void main(String[] args){ Info info = new Info(); Producptor p = new Producptor(info); Consumer c = new Consumer(info); new Thread(p).start(); new Thread(c).start(); } }
所有的設定和取得資料的操作都交給了同步方法完成
現在同步問題解決了,但是重複問題更嚴重了
解決重複操作
如果要想解決重複問題,那麼必須加入等待與喚醒的處理機制,而這樣的操作方法是有 Object 類所提供的
在 Object 類中提供有如下幾種方法:
等待: public final void wait() throws InterruptedException{}
喚醒第一個等待執行緒: public final void notify();
喚醒全部等待執行緒: public final void notifyAll();
範例:修改Info類
package cn.mysterious.actualcombat; class Info{ private String title; private String content; private boolean flag = true; // flag = true 表示可以生產,但是不允許取走資料 // flag = false 表示可以取走資料,但是不允許生產資料 public synchronized void set(String title, String content){ if (this.flag = false) { // 表示已經生產過了,還未取走 try { super.wait(); // 等待 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } // 沒有生產,可以生產 this.title = title; try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } this.content = content; this.flag = false;// 表示生產過了 super.notify(); } public synchronized void get(){ if (this.flag = true) { // 此時應該生產,不應該取走資料 try { super.wait(); // 等待 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(this.title + "-->" +this.content ); this.flag = true; // 表示取走了 super.notify(); } } class Producptor implements Runnable{ private Info info = null; public Producptor(Info info){ this.info = info; } @Override public void run() { // TODO Auto-generated method stub for (int i = 0; i < 50; i++) { if (i % 2 == 0) { // 偶數 this.info.set("帥帥","一個學生"); }else { this.info.set("可愛的動物","小貓咪"); } } } } class Consumer implements Runnable{ private Info info = null; public Consumer(Info info){ this.info = info; } @Override public void run() { // TODO Auto-generated method stub for (int i = 0; i < 50; i++) { this.info.get(); } } } public class ActualCombat{ public static void main(String[] args){ Info info = new Info(); Producptor p = new Producptor(info); Consumer c = new Consumer(info); new Thread(p).start(); new Thread(c).start(); } }
面試題:請解釋 sleep() 與 wait() 的區別?
sleep() 是 Thread 類定義的方法,在休眠一定時間之後自己喚醒
wait() 是 Object 類定義的方法,表示執行緒要等待執行,必須通過 notify(),notifyAll() 方法來進行喚醒
總結
生產者和消費者這是一個模型,完整的體現了執行緒的同步, Object 類的支援