生產者消費者問題理解與Java實現

生產者消費者問題(英語:Producer-consumer problem),也稱有限緩衝問題(英語:Bounded-buffer problem),是一個多線程同步問題的經典案例。該問題描述了兩個共享固定大小緩衝區的線程——即所謂的「生產者」和「消費者」——在實際運行時會發生的問題。生產者的主要做用是生成必定量的數據放到緩衝區中,而後重複此過程。與此同時,消費者也在緩衝區消耗這些數據。該問題的關鍵就是要保證生產者不會在緩衝區滿時加入數據,消費者也不會在緩衝區中空時消耗數據。 html

要解決該問題,就必須讓生產者在緩衝區滿時休眠(要麼乾脆就放棄數據),等到下次消費者消耗緩衝區中的數據的時候,生產者才能被喚醒,開始往緩衝區添加數據。一樣,也可讓消費者在緩衝區空時進入休眠,等到生產者往緩衝區添加數據以後,再喚醒消費者。一般採用進程間通訊的方法解決該問題,經常使用的方法有信號燈法等。若是解決方法不夠完善,則容易出現死鎖的狀況。出現死鎖時,兩個線程都會陷入休眠,等待對方喚醒本身。該問題也能被推廣到多個生產者和消費者的情形。 java

Java代碼模擬生產者-消費者
產品類
linux

package org.dennist.thread.demo;
/**
 *  Product.java    
 */
public class Product {    //產品類

    private int productId = 0;
    
    public Product(int productId){
        this.productId = productId;
    }

    public int getProductId() {
        return productId;
    }
    
    
    public void setProductId(int productId) {
        this.productId = productId;
    }

    @Override
    public String toString() {
        return ""+productId;
    }

}




倉庫類 c++

package org.dennist.thread.demo;

public class StoreHouse {

    private int base = 0;
    private int top = 0;
    //倉庫大小
    private Product[] products = new Product[10];
    
    /**
     * 生產產品
     * @param product
     */
    public synchronized void push(Product product){
        if(top==products.length){    //若是倉庫已滿,等待消費
            try {
                System.out.println("倉庫已滿,正在等待消費..");
                wait();
            }catch (InterruptedException e) { 
                System.out.println("stop push product because other reasons"); 

            } 
        }
        //倉庫未滿,將生產的產品入庫
        products[top] = product;
        //庫中產品數量+1
        top++;
    }
    
    /**
     * 消費產品
     * @return
     */
    public synchronized Product pop() { 
        Product product = null; 
        while (top == base) {     //倉庫未空,不能消費
            notify(); 
            try { 
                System.out.println("倉庫已空,正等待生產..."); 
                wait(); 
            } catch (InterruptedException e) { 
                System.out.println("stop push product because other reasons"); 

            } 
        } 
        //倉庫未空,等待消費
        top--; 
        product = products[top]; 
        products[top] = null; 
        return product; 
    } 
    
}



生產者類 數組

package org.dennist.thread.demo;

public class Producer implements Runnable{

    private String producerName ;
    private StoreHouse storeHouse ;
    
    public Producer(String producerName, StoreHouse storeHouse) { 
        this.producerName = producerName; 
        this.storeHouse = storeHouse; 
    } 
 
    public void setProducerName(String producerName) { 
        this.producerName = producerName; 
    } 
 
    public String getProducerName() { 
        return producerName; 
    } 
     
    
    @Override
    public void run() {
        execProcuct();
    }

    private void execProcuct() {
        int i = 0;
        while(true){
             i++; 
             Product pro = new Product(i); 
             storeHouse.push(pro); 
             System.out.println(getProducerName() + " 生產了 " + pro); 
             try { 
                 Thread.sleep(2000); 
             } catch (InterruptedException e) { 
                 return; 
             } 
        }
    }

}




消費者類 安全

package org.dennist.thread.demo;

public class Consumer implements Runnable{

    private String consumerName = null; 
    
    private StoreHouse storeHouse = null; 
 
    public Consumer(String consumerName, StoreHouse storeHouse) { 
        this.consumerName = consumerName; 
        this.storeHouse = storeHouse; 
    } 
     
    public void setConsumerName(String consumerName) { 
        this.consumerName = consumerName; 
    } 
     
    public String getConsumerName() { 
        return consumerName; 
    } 
     
    public void execConsume() { 
        while (true) { 
            System.out.println(getConsumerName() + " 消費了 " + storeHouse.pop()); 
            try { 
                Thread.sleep(5000); 
            } catch (InterruptedException e) { 
                return; 
            } 
        } 
    } 
 
  
    @Override
    public void run() {
         execConsume(); 
    }

}




測試主類 網絡

package org.dennist.thread.demo;

public class TestPC {

    public static void main(String[] args) {
         StoreHouse storeHouse = new StoreHouse(); 
         Producer producer = new Producer("生產者", storeHouse); 
         Consumer comsumer = new Consumer("消費者", storeHouse); 
         Thread t1 = new Thread(producer); 
         Thread t2 = new Thread(comsumer); 
         t1.start(); 
         t2.start(); 
    }
}




關於JAVA多線程同步 多線程

JAVA多線程同步主要依賴於若干方法和關鍵字 app

1  wait方法: ide

        該方法屬於Object的方法,wait方法的做用是使得當前調用wait方法所在部分(代碼塊)的線程中止執行,並釋放當前得到的調用wait所在的代碼塊的鎖,並在其餘線程調用notify或者notifyAll方法時恢復到競爭鎖狀態(一旦得到鎖就恢復執行)。

        調用wait方法須要注意幾點:

        第一點:wait被調用的時候必須在擁有鎖(即synchronized修飾的)的代碼塊中。

        第二點:恢復執行後,從wait的下一條語句開始執行,於是wait方法老是應當在while循環中調用,以避免出現恢復執行後繼續執行的條件不知足卻繼續執行的狀況。

        第三點:若wait方法參數中帶時間,則除了notifynotifyAll被調用能激活處於wait狀態(等待狀態)的線程進入鎖競爭外,在其餘線程中interrupt它或者參數時間到了以後,該線程也將被激活到競爭狀態。

        第四點:wait方法被調用的線程必須得到以前執行到wait時釋放掉的鎖從新得到纔可以恢復執行。

2  notify方法和notifyAll方法:

        notify方法通知調用了wait方法,可是還沒有激活的一個線程進入線程調度隊列(即進入鎖競爭),注意不是當即執行。而且具體是哪個線程不能保證。另一點就是被喚醒的這個線程必定是在等待wait所釋放的鎖。

        notifyAll方法則喚醒全部調用了wait方法,還沒有激活的進程進入競爭隊列。

3 synchronized關鍵字:

        第一點:synchronized用來標識一個普通方法時,表示一個線程要執行該方法,必須取得該方法所在的對象的鎖。

        第二點:synchronized用來標識一個靜態方法時,表示一個線程要執行該方法,必須得到該方法所在的類的類鎖。

        第三點:synchronized修飾一個代碼塊。相似這樣:synchronized(obj) { //code.... }。表示一個線程要執行該代碼塊,必須得到obj的鎖。這樣作的目的是減少鎖的粒度,保證當不一樣塊所需的鎖不衝突時不用對整個對象加鎖。利用零長度的byte數組對象作obj很是經濟。

4 atomic action(原子操做):

        在JAVA中,如下兩點操做是原子操做。可是cc++中並不如此。

        第一點:對引用變量和除了longdouble以外的原始數據類型變量進行讀寫。

        第二點:對全部聲明爲volatile的變量(包括longdouble)的讀寫。

        另外:在java.util.concurrentjava.util.concurrent.atomic包中提供了一些不依賴於同步機制的線程安全的類和方法。

 

附錄:

進程間通訊

進程間通訊(IPCInter-Process Communication),指至少兩個進程或線程間傳送數據或信號的一些技術或方法。線程是計算機系統分配資源的最小單位。每一個進程都有本身的一部分獨立的系統資源,彼此是隔離的。爲了能使不一樣的進程互相訪問資源並進行協調工做,纔有了進程間通訊。這些進程能夠運行在同一計算機上或網絡鏈接的不一樣計算機上。

進程間通訊技術包括消息傳遞、同步、共享內存和遠程過程調用。IPC是一種標準的Unix通訊機制。

主要的IPC方法有

    1)管道(Pipe):管道可用於具備親緣關係進程間的通訊,容許一個進程和另外一個與它有共同祖先的進程之間進行通訊。

  (2)命名管道(named pipe):命名管道克服了管道沒有名字的限制,所以,除具備管道所具備的功能外,它還容許無親緣關係進程間的通訊。命名管道在文件系統中有對應的文件名。命名管道經過命令mkfifo或系統調用mkfifo來建立。

  (3)信號(Signal):信號是比較複雜的通訊方式,用於通知接受進程有某種事件發生,除了用於進程間通訊外,進程還能夠發送信號給進程自己;linux除了支持Unix早期信號語義函數sigal外,還支持語義符合Posix.1標準的信號函數sigaction(實際上,該函數是基於BSD的,BSD爲了實現可靠信號機制,又可以統一對外接口,用sigaction函數從新實現了signal函數)。

  (4)消息(Message)隊列:消息隊列是消息的連接表,包括Posix消息隊列system V消息隊列。有足夠權限的進程能夠向隊列中添加消息,被賦予讀權限的進程則能夠讀走隊列中的消息。消息隊列克服了信號承載信息量少,管道只能承載無格式字節流以及緩衝區大小受限等缺

  (5)共享內存:使得多個進程能夠訪問同一塊內存空間,是最快的可用IPC形式。是針對其餘通訊機制運行效率較低而設計的。每每與其它通訊機制,如信號量結合使用,來達到進程間的同步及互斥。

  (6)內存映射(mapped memory):內存映射容許任何多個進程間通訊,每個使用該機制的進程經過把一個共享的文件映射到本身的進程地址空間來實現它。

  (7)信號量(semaphore):主要做爲進程間以及同一進程不一樣線程之間的同步手段。

  (8)套接口(Socket):更爲通常的進程間通訊機制,可用於不一樣機器之間的進程間通訊。起初是由Unix系統的BSD分支開發出來的,但如今通常能夠移植到其它類Unix系統上:LinuxSystem V的變種都支持套接字。

參考資料http://blog.sina.com.cn/s/blog_57609a2a0100bcdv.html

參考資料:http://lanvis.blog.163.com/blog/static/26982162009798422547/

轉載請註明出處:[http://www.cnblogs.com/dennisit/archive/2013/02/25/2931573.html]

相關文章
相關標籤/搜索