Java多執行緒——生產者和消費者模式

gary-liu發表於2017-03-18

生產者消費者模式

生產者和消費者模式是一種併發設計模式,生產者消費者模式解決的是兩者速率不一致而產生的阻抗不匹配,該模式通過平衡生產執行緒和消費執行緒的工作能力來提高程式的整體處理資料的速度。

生產者消費者模式是通過一個容器來解決生產者和消費者的強耦合問題。生產者和消費者彼此之間不直接通訊,而通過阻塞佇列來進行通訊,所以生產者生產完資料之後不用等待消費者處理,直接扔給阻塞佇列,消費者不找生產者要資料,而是直接從阻塞佇列裡取,阻塞佇列就相當於一個緩衝區,平衡了生產者和消費者的處理能力。

為什麼要使用生產者和消費者模式

在多執行緒開發當中,如果生產者處理速度很快,而消費者處理速度很慢,那麼生產者就必須等待消費者處理完,才能繼續生產資料。同樣的道理,如果消費者的處理能力大於生產者,那麼消費者就必須等待生產者。為了解決這個問題於是引入了生產者和消費者模式。

優點

可以獨立地同時編碼生產者和消費者,他們只需要知道共享物件即可。

生產者不需要知道誰是消費者或有多少消費者,消費者也是如此。

生產者和消費者可以以不同的速度工作,消費者沒有消費半成品的風險。

分離生產者和消費者的功能導致更乾淨,可讀和易於管理的程式碼。

應用

Executor框架本身也實現了生產者和消費者模式,線上程池中,如果任務數多於基本執行緒數時,會將任務放到阻塞佇列中來平衡生產者和消費者的處理能力,關於執行緒池的介紹可以看我的另一篇文章 java——執行緒池

示例程式碼

用阻塞佇列實現

先用阻塞佇列來實現,BlockingQueue 是個繼承Queue介面的介面,該介面有不同的實現,比如ArrayBlockingQueue 和 LinkedBlockingQueue,他們都實現了 FIFO。

用LinkedBlockingQueue實現生產者和消費者模式如下。

public class ProducerConsumerPractice {

    public static void main(String[] args){

        LinkedBlockingDeque<Integer> linkedBlockingDeque = new LinkedBlockingDeque<>(5);
        new Thread(new Producer(linkedBlockingDeque)).start();
        new Thread(new Consumer(linkedBlockingDeque)).start();
    }
}

class Producer implements Runnable{

    private LinkedBlockingDeque<Integer> linkedBlockingDeque;

    public Producer(LinkedBlockingDeque<Integer> linkedBlockingDeque){
        this.linkedBlockingDeque = linkedBlockingDeque;
    }

    public void run(){
        for(int i = 0; i < 10; i++){
            try {
                //Thread.sleep(500);
                linkedBlockingDeque.put(i);
                System.out.println("Producer: " + i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

class Consumer implements Runnable{

    private LinkedBlockingDeque<Integer> linkedBlockingDeque;

    public Consumer(LinkedBlockingDeque<Integer> linkedBlockingDeque){
        this.linkedBlockingDeque = linkedBlockingDeque;
    }

    public void run(){
        while(true){
            try{
                Thread.sleep(500);
                System.out.println("consumer: " + linkedBlockingDeque.take());
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

執行結果:

Producer: 0
Producer: 1
Producer: 2
Producer: 3
Producer: 4
consumer: 0
Producer: 5
consumer: 1
Producer: 6
consumer: 2
Producer: 7
consumer: 3
Producer: 8
consumer: 4
Producer: 9
consumer: 5
consumer: 6
consumer: 7
consumer: 8
consumer: 9

我設定了阻塞佇列的初始長度為5,然後用sleep(500)調慢了消費速度,所以我們在執行結果中可以看到生產0-4後,佇列滿了,生產者被阻塞了,然後消費者根據FIFO原則先消費了0,所以生產者又可以繼續生產了。在ide中執行看的會更清楚些,第二種方式實現列印的結果會更明白。

用wait(), notify()實現

之前寫過一篇文章 執行緒間協作——wait、notify、notifyAll 講了 wait(), notify(),notifyAll()的用法,現在用他們來實現生產者和消費者模式,當做補充例子吧。這裡用 Vector 模擬佇列,因為這個佇列沒有阻塞功能,所以要用wait()和 notify()模擬佇列滿時生產者和佇列為空時消費者的阻塞,以及正常情況下互相通知對方的效果。

程式碼中同樣調慢了消費速度,為了看的更清晰。

public class ProducerConsumerPractice {

    public static void main(String[] args){

        Vector<Integer> vector = new Vector<>(5);
        new Thread(new Producer(vector)).start();
        new Thread(new Consumer(vector)).start();
    }
}


class Producer implements Runnable{

    private Vector<Integer> vector;

    public Producer(Vector vector){
        this.vector = vector;
    }

    public void run(){
        for(int i = 0; i < 10; i++){
            while(vector.size() == vector.capacity()){
                synchronized (vector){
                    System.out.println("Queue is full, Producer  is waiting , size: " + vector.size());
                    try {
                        vector.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }

            synchronized (vector){
                vector.add(i);
                System.out.println("Producer: " + i);
                vector.notifyAll();
            }
        }
    }
}

class Consumer implements Runnable{

    private Vector<Integer> vector;

    public Consumer(Vector vector){
        this.vector = vector;
    }

    public void run(){
        while(true){
            while(vector.isEmpty()){
                synchronized (vector){
                    System.out.println("Queue is empty, Consumer is waiting , size: " + vector.size());
                    try {
                        vector.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            }
            //調慢消費速度
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (vector){
                System.out.println("Consumer: " + vector.remove(0));
                vector.notifyAll();
            }
        }
    }

}

執行結果:

Producer: 0
Producer: 1
Producer: 2
Producer: 3
Producer: 4
Queue is full, Producer  is waiting , size: 5
Consumer: 0
Producer: 5
Queue is full, Producer  is waiting , size: 5
Consumer: 1
Producer: 6
Queue is full, Producer  is waiting , size: 5
Consumer: 2
Producer: 7
Queue is full, Producer  is waiting , size: 5
Consumer: 3
Producer: 8
Queue is full, Producer  is waiting , size: 5
Consumer: 4
Producer: 9
Consumer: 5
Consumer: 6
Consumer: 7
Consumer: 8
Consumer: 9
Queue is empty, Consumer is waiting , size: 0

參考資料

聊聊併發——生產者消費者模式
Producer Consumer Problem with Wait and Notify Example

相關文章