Java多線程系列--“基礎篇”11之 生產消費者問題


 

概要

本章,會對“生產/消費者問題”進行討論。涉及到的內容包括:
1. 生產/消費者模型
2. 生產/消費者實現

轉載請注明出處:http://www.cnblogs.com/skywang12345/p/3480016.html

 

1. 生產/消費者模型

生產/消費者問題是個非常典型的多線程問題,涉及到的對象包括“生產者”、“消費者”、“倉庫”和“產品”。他們之間的關系如下:
(01) 生產者僅僅在倉儲未滿時候生產,倉滿則停止生產。
(02) 消費者僅僅在倉儲有產品時候才能消費,倉空則等待。
(03) 當消費者發現倉儲沒產品可消費時候會通知生產者生產。
(04) 生產者在生產出可消費產品時候,應該通知等待的消費者去消費。

 

2. 生產/消費者實現

下面通過wait()/notify()方式實現該模型(后面在學習了線程池相關內容之后,再通過其它方式實現生產/消費者模型)。源碼如下:

  1 // Demo1.java
  2 // 倉庫
  3 class Depot {
  4     private int capacity;    // 倉庫的容量
  5     private int size;        // 倉庫的實際數量
  6 
  7     public Depot(int capacity) {
  8         this.capacity = capacity;
  9         this.size = 0;
 10     }
 11 
 12     public synchronized void produce(int val) {
 13         try {
 14              // left 表示“想要生產的數量”(有可能生產量太多,需多此生產)
 15             int left = val;
 16             while (left > 0) {
 17                 // 庫存已滿時,等待“消費者”消費產品。
 18                 while (size >= capacity)
 19                     wait();
 20                 // 獲取“實際生產的數量”(即庫存中新增的數量)
 21                 // 如果“庫存”+“想要生產的數量”>“總的容量”,則“實際增量”=“總的容量”-“當前容量”。(此時填滿倉庫)
 22                 // 否則“實際增量”=“想要生產的數量”
 23                 int inc = (size+left)>capacity ? (capacity-size) : left;
 24                 size += inc;
 25                 left -= inc;
 26                 System.out.printf("%s produce(%3d) --> left=%3d, inc=%3d, size=%3d\n", 
 27                         Thread.currentThread().getName(), val, left, inc, size);
 28                 // 通知“消費者”可以消費了。
 29                 notifyAll();
 30             }
 31         } catch (InterruptedException e) {
 32         }
 33     } 
 34 
 35     public synchronized void consume(int val) {
 36         try {
 37             // left 表示“客戶要消費數量”(有可能消費量太大,庫存不夠,需多此消費)
 38             int left = val;
 39             while (left > 0) {
 40                 // 庫存為0時,等待“生產者”生產產品。
 41                 while (size <= 0)
 42                     wait();
 43                 // 獲取“實際消費的數量”(即庫存中實際減少的數量)
 44                 // 如果“庫存”<“客戶要消費的數量”,則“實際消費量”=“庫存”;
 45                 // 否則,“實際消費量”=“客戶要消費的數量”。
 46                 int dec = (size<left) ? size : left;
 47                 size -= dec;
 48                 left -= dec;
 49                 System.out.printf("%s consume(%3d) <-- left=%3d, dec=%3d, size=%3d\n", 
 50                         Thread.currentThread().getName(), val, left, dec, size);
 51                 notifyAll();
 52             }
 53         } catch (InterruptedException e) {
 54         }
 55     }
 56 
 57     public String toString() {
 58         return "capacity:"+capacity+", actual size:"+size;
 59     }
 60 } 
 61 
 62 // 生產者
 63 class Producer {
 64     private Depot depot;
 65     
 66     public Producer(Depot depot) {
 67         this.depot = depot;
 68     }
 69 
 70     // 消費產品:新建一個線程向倉庫中生產產品。
 71     public void produce(final int val) {
 72         new Thread() {
 73             public void run() {
 74                 depot.produce(val);
 75             }
 76         }.start();
 77     }
 78 }
 79 
 80 // 消費者
 81 class Customer {
 82     private Depot depot;
 83     
 84     public Customer(Depot depot) {
 85         this.depot = depot;
 86     }
 87 
 88     // 消費產品:新建一個線程從倉庫中消費產品。
 89     public void consume(final int val) {
 90         new Thread() {
 91             public void run() {
 92                 depot.consume(val);
 93             }
 94         }.start();
 95     }
 96 }
 97 
 98 public class Demo1 {  
 99     public static void main(String[] args) {  
100         Depot mDepot = new Depot(100);
101         Producer mPro = new Producer(mDepot);
102         Customer mCus = new Customer(mDepot);
103 
104         mPro.produce(60);
105         mPro.produce(120);
106         mCus.consume(90);
107         mCus.consume(150);
108         mPro.produce(110);
109     }
110 }

說明
(01) Producer是“生產者”類,它與“倉庫(depot)”關聯。當調用“生產者”的produce()方法時,它會新建一個線程並向“倉庫”中生產產品。
(02) Customer是“消費者”類,它與“倉庫(depot)”關聯。當調用“消費者”的consume()方法時,它會新建一個線程並消費“倉庫”中的產品。
(03) Depot是“倉庫”類,倉庫中記錄“倉庫的容量(capacity)”以及“倉庫中當前產品數目(size)”。
        “倉庫”類的生產方法produce()和消費方法consume()方法都是synchronized方法,進入synchronized方法體,意味着這個線程獲取到了該“倉庫”對象的同步鎖。這也就是說,同一時間,生產者和消費者線程只能有一個能運行。通過同步鎖,實現了對“殘酷”的互斥訪問。
       對於生產方法produce()而言:當倉庫滿時,生產者線程等待,需要等待消費者消費產品之后,生產線程才能生產;生產者線程生產完產品之后,會通過notifyAll()喚醒同步鎖上的所有線程,包括“消費者線程”,即我們所說的“通知消費者進行消費”。
      對於消費方法consume()而言:當倉庫為空時,消費者線程等待,需要等待生產者生產產品之后,消費者線程才能消費;消費者線程消費完產品之后,會通過notifyAll()喚醒同步鎖上的所有線程,包括“生產者線程”,即我們所說的“通知生產者進行生產”。

(某一次)運行結果

Thread-0 produce( 60) --> left=  0, inc= 60, size= 60
Thread-4 produce(110) --> left= 70, inc= 40, size=100
Thread-2 consume( 90) <-- left=  0, dec= 90, size= 10
Thread-3 consume(150) <-- left=140, dec= 10, size=  0
Thread-1 produce(120) --> left= 20, inc=100, size=100
Thread-3 consume(150) <-- left= 40, dec=100, size=  0
Thread-4 produce(110) --> left=  0, inc= 70, size= 70
Thread-3 consume(150) <-- left=  0, dec= 40, size= 30
Thread-1 produce(120) --> left=  0, inc= 20, size= 50

 


更多內容

00. Java多線程系列目錄(共xx篇)

01. Java多線程系列--“基礎篇”01之 基本概念

02. Java多線程系列--“基礎篇”02之 常用的實現多線程的兩種方式

03. Java多線程系列--“基礎篇”03之 Thread中start()和run()的區別

04. Java多線程系列--“基礎篇”04之 synchronized關鍵字

05. Java多線程系列--“基礎篇”05之 線程等待與喚醒

06. Java多線程系列--“基礎篇”06之 線程讓步

07. Java多線程系列--“基礎篇”07之 線程休眠 

08. Java多線程系列--“基礎篇”08之 join()

09. Java多線程系列--“基礎篇”09之 interrupt()和線程終止方式

10. Java多線程系列--“基礎篇”10之 線程優先級和守護線程

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM