1. 程式人生 > >設計模式06---生產者消費者模式

設計模式06---生產者消費者模式

生產者和消費者指的是兩個不同的執行緒類物件,操作統一資源的情況。具體的操作流程如下:

(1)生產者負責生成資料,消費者負責取走資料;

(2)生產者每生產完一組資料之後,消費者就要取走一組資料。

一. 直白寫法

1. info類

 1 public class Info {
 2     private String name;
 3     private double price;
 4     public String getName() {
 5         return name;
 6     }
 7     public void setName(String name) {
8 this.name = name; 9 } 10 public double getPrice() { 11 return price; 12 } 13 public void setPrice(double price) { 14 this.price = price; 15 } 16 17 }
View Code

2. 生產者類

 1 public class Producer implements Runnable{
 2     private Info info;
3 public Producer(Info info) { 4 this.info=info; 5 } 6 7 @Override 8 public void run() { 9 for(int i=0;i<20;i++) { 10 if(i%2==0) { 11 this.info.setName("鉛筆"); 12 try { 13 Thread.sleep(100); 14 } catch
(InterruptedException e) { 15 // TODO Auto-generated catch block 16 e.printStackTrace(); 17 } 18 this.info.setPrice(1.0); 19 }else { 20 this.info.setName("LV"); 21 try { 22 Thread.sleep(100); 23 } catch (InterruptedException e) { 24 // TODO Auto-generated catch block 25 e.printStackTrace(); 26 } 27 this.info.setPrice(10000.0); 28 } 29 } 30 } 31 32 }
View Code

3. 消費者類

 1 public class Consumer implements Runnable{
 2     private Info info;
 3     public Consumer(Info info) {
 4         this.info=info;
 5     }
 6     @Override
 7     public void run() {
 8         for(int i=0;i<20;i++) {
 9             try {
10                 Thread.sleep(100);
11             } catch (InterruptedException e) {
12                 // TODO Auto-generated catch block
13                 e.printStackTrace();
14             }
15             System.out.println(this.info.getName()+"----"+this.info.getPrice());
16         }    
17     }
18 }
View Code

4. 測試類

1 public class Test {
2     public static void main(String[] args) {
3         Info info=new Info();
4         new Thread(new Producer(info)).start();
5         new Thread(new Consumer(info)).start();
6     }
7 }
View Code
 1 LV----1.0
 2 鉛筆----10000.0
 3 鉛筆----1.0
 4 鉛筆----10000.0
 5 鉛筆----1.0
 6 LV----1.0
 7 鉛筆----1.0
 8 LV----10000.0
 9 鉛筆----1.0
10 LV----1.0
11 鉛筆----1.0
12 LV----10000.0
13 鉛筆----10000.0
14 LV----10000.0
15 鉛筆----10000.0
16 LV----10000.0
17 鉛筆----1.0
18 LV----1.0
19 鉛筆----1.0
20 LV----10000.0
View Code

說明:上面添加了sleep方法,是為了更好的觀察分析存在的問題。根據測試結果我們可以知道,有兩個很嚴重的問題。第一個是資料錯亂,比如鉛筆對應的是10000;第二個是資料重複設定,比如鉛筆連著列印了幾次。不符合生產一個,然後再消費一個的標準。因此,需要進行改進。

二. 解決資料錯亂

資料的錯亂完全是因為非同步的操作完成的,所以應該使用同步處理。因為取和設定是兩個不同的操作,要想進行同步操作,因此需要將它們定義再一個類裡面。

1. info類

 1 public class Info {
 2     private String name;
 3     private double price;
 4     public synchronized void set(String name,double price) {
 5         this.name=name;
 6         try {
 7             Thread.sleep(100);
 8         } catch (InterruptedException e) {
 9             // TODO Auto-generated catch block
10             e.printStackTrace();
11         }
12         this.price=price;
13     }
14     
15     public synchronized void get() {
16         try {
17             Thread.sleep(100);
18         } catch (InterruptedException e) {
19             // TODO Auto-generated catch block
20             e.printStackTrace();
21         }
22         System.out.println(this.name+"---------"+this.price);
23     }
24 
25 }
View Code

2.生產者

 1 public class Producer implements Runnable{
 2     private Info info;
 3     public Producer(Info info) {
 4         this.info=info;
 5     }
 6     
 7     @Override
 8     public void run() {
 9         for(int i=0;i<20;i++) {
10             if(i%2==0) {
11                 this.info.set("鉛筆", 1.0);
12             }else {
13                 this.info.set("LV", 10000.0);
14             }
15         }
16     }
17 
18 }
View Code

3.消費者

 1 public class Consumer implements Runnable{
 2     private Info info;
 3     public Consumer(Info info) {
 4         this.info=info;
 5     }
 6     @Override
 7     public void run() {
 8         for(int i=0;i<20;i++) {
 9             this.info.get();
10         }    
11     }
12 }
View Code

4. 測試類

1 public class Test {
2     public static void main(String[] args) {
3         Info info=new Info();
4         new Thread(new Producer(info)).start();
5         new Thread(new Consumer(info)).start();
6     }
7 }
View Code
 1 鉛筆---------1.0
 2 鉛筆---------1.0
 3 LV---------10000.0
 4 LV---------10000.0
 5 LV---------10000.0
 6 LV---------10000.0
 7 LV---------10000.0
 8 鉛筆---------1.0
 9 鉛筆---------1.0
10 鉛筆---------1.0
11 LV---------10000.0
12 LV---------10000.0
13 鉛筆---------1.0
14 鉛筆---------1.0
15 鉛筆---------1.0
16 LV---------10000.0
17 LV---------10000.0
18 鉛筆---------1.0
19 鉛筆---------1.0
20 鉛筆---------1.0
View Code

說明:現在資料錯亂的問題解決了,但是資料重複設定的問題更嚴重了。需要進一步解決

三. 解決重複設定問題

解決重複設定問題就需要有一個標誌位來告訴是該生產還是是該消費。比如下面修改就可以解決:

1. Info類

 1 package com.test.b;
 2 
 3 public class Info {
 4     private String name;
 5     private double price;
 6     private boolean flag=true;
 7     //flag=true:表示可以生產,但是不可以取走
 8     //flag=false:表示可以取走,但是不可以生產
 9     public synchronized void set(String name,double price) {
10         //重複進入到了set()方法裡面,發現不能夠生產,因此需要進行等待
11         if(flag==false) {
12             try {
13                 super.wait();
14             } catch (InterruptedException e) {
15                 // TODO Auto-generated catch block
16                 e.printStackTrace();
17             }
18         }
19         this.name=name;
20         try {
21             Thread.sleep(100);
22         } catch (InterruptedException e) {
23             // TODO Auto-generated catch block
24             e.printStackTrace();
25         }
26         this.price=price;
27         this.flag=false;//修改生產標記,說明設定完畢,現在可以進行消費了
28         super.notify();//喚醒其它等待執行緒
29     }
30     
31     public synchronized void get() {
32         if(this.flag==true) {//還沒生產呢,但是進入到了get方法裡面來,需要等待
33             try {
34                 super.wait();
35             } catch (InterruptedException e) {
36                 // TODO Auto-generated catch block
37                 e.printStackTrace();
38             }
39         }
40         try {
41             Thread.sleep(100);
42         } catch (InterruptedException e) {
43             // TODO Auto-generated catch block
44             e.printStackTrace();
45         }
46         System.out.println(this.name+"---------"+this.price);
47         this.flag=true;//消費完畢,說明現在可以進行生產了
48         super.notify();
49     }
50 
51 }
View Code

2. 生產者

 1 public class Producer implements Runnable{
 2     private Info info;
 3     public Producer(Info info) {
 4         this.info=info;
 5     }
 6     
 7     @Override
 8     public void run() {
 9         for(int i=0;i<20;i++) {
10             if(i%2==0) {
11                 this.info.set("鉛筆", 1.0);
12             }else {
13                 this.info.set("LV", 10000.0);
14             }
15         }
16     }
17 
18 }
View Code

3. 消費者

 1 public class Consumer implements Runnable{
 2     private Info info;
 3     public Consumer(Info info) {
 4         this.info=info;
 5     }
 6     @Override
 7     public void run() {
 8         for(int i=0;i<20;i++) {
 9             this.info.get();
10         }    
11     }
12 }
View Code

4. 測試

1 public class Test {
2     public static void main(String[] args) {
3         Info info=new Info();
4         new Thread(new Producer(info)).start();
5         new Thread(new Consumer(info)).start();
6     }
7 }
View Code
 1 鉛筆---------1.0
 2 LV---------10000.0
 3 鉛筆---------1.0
 4 LV---------10000.0
 5 鉛筆---------1.0
 6 LV---------10000.0
 7 鉛筆---------1.0
 8 LV---------10000.0
 9 鉛筆---------1.0
10 LV---------10000.0
11 鉛筆---------1.0
12 LV---------10000.0
13 鉛筆---------1.0
14 LV---------10000.0
15 鉛筆---------1.0
16 LV---------10000.0
17 鉛筆---------1.0
18 LV---------10000.0
19 鉛筆---------1.0
20 LV---------10000.0
View Code

Note:生產者和消費者都需要實現Runnable介面。