生產者消費者模式
生產者和消費者模式是一種併發設計模式,生產者消費者模式解決的是兩者速率不一致而產生的阻抗不匹配,該模式通過平衡生產線程和消費線程的工作能力來提高程序的整體處理數據的速度。
生產者消費者模式是通過一個容器來解決生產者和消費者的強耦合問題。生產者和消費者彼此之間不直接通訊,而通過阻塞隊列來進行通訊,所以生產者生產完數據之後不用等待消費者處理,直接扔給阻塞隊列,消費者不找生產者要數據,而是直接從阻塞隊列裏取,阻塞隊列就相當於一個緩衝區,平衡了生產者和消費者的處理能力。
爲什麼要使用生產者和消費者模式
在多線程開發當中,如果生產者處理速度很快,而消費者處理速度很慢,那麼生產者就必須等待消費者處理完,才能繼續生產數據。同樣的道理,如果消費者的處理能力大於生產者,那麼消費者就必須等待生產者。爲了解決這個問題於是引入了生產者和消費者模式。
優點
可以獨立地同時編碼生產者和消費者,他們只需要知道共享對象即可。
生產者不需要知道誰是消費者或有多少消費者,消費者也是如此。
生產者和消費者可以以不同的速度工作,消費者沒有消費半成品的風險。
分離生產者和消費者的功能導致更乾淨,可讀和易於管理的代碼。
應用
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