天天看點

阻塞隊列實作生産者消費者模式

生産者消費者模式是并發、多線程程式設計中經典的設計模式,生産者和消費者通過分離的執行工作解耦,簡化了開發模式,生産者和消費者可以以不同的速度生産和消費資料。這篇文章我們來看看什麼是生産者消費者模式,這個問題也是多線程面試題中經常被提及的。如何使用阻塞隊列(Blocking Queue)解決生産者消費者模式,以及使用生産者消費者模式的好處。

真實世界中的生産者消費者模式

生産者和消費者模式在生活當中随處可見,它描述的是協調與協作的關系。比如一個人正在準備食物(生産者),而另一個人正在吃(消費者),他們使用一個共用的桌子用于放置盤子和取走盤子,生産者準備食物,如果桌子上已經滿了就等待,消費者(那個吃的)等待如果桌子空了的話。這裡桌子就是一個共享的對象。在Java Executor架構自身實作了生産者消費者模式它們分别負責添加和執行任務。

生産者消費者模式的好處

它的确是一種實用的設計模式,常用于編寫多線程或并發代碼。下面是它的一些優點:

它簡化的開發,你可以獨立地或并發的編寫消費者和生産者,它僅僅隻需知道共享對象是誰

生産者不需要知道誰是消費者或者有多少消費者,對消費者來說也是一樣

生産者和消費者可以以不同的速度執行

分離的消費者和生産者在功能上能寫出更簡潔、可讀、易維護的代碼

多線程中的生産者消費者問題

生産者消費者問題是一個流行的面試題,面試官會要求你實作生産者消費者設計模式,以至于能讓生産者應等待如果隊列或籃子滿了的話,消費者等待如果隊列或者籃子是空的。這個問題可以用不同的方式來現實,經典的方法是使用wait和notify方法在生産者和消費者線程中合作,在隊列滿了或者隊列是空的條件下阻塞,Java5的阻塞隊列(BlockingQueue)資料結構更簡單,因為它隐含的提供了這些控制,現在你不需要使用wait和nofity在生産者和消費者之間通信了,阻塞隊列的put()方法将阻塞如果隊列滿了,隊列take()方法将阻塞如果隊列是空的。在下部分我們可以看到代碼例子。

使用阻塞隊列實作生産者消費者模式

阻塞隊列實作生産者消費者模式超級簡單,它提供開箱即用支援阻塞的方法put()和take(),開發者不需要寫困惑的wait-nofity代碼去實作通信。BlockingQueue 一個接口,Java5提供了不同的現實,如ArrayBlockingQueue和LinkedBlockingQueue,兩者都是先進先出(FIFO)順序。而ArrayLinkedQueue是自然有界的,LinkedBlockingQueue可選的邊界。下面這是一個完整的生産者消費者代碼例子,對比傳統的wait、nofity代碼,它更易于了解。

import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.logging.Level; import java.util.logging.Logger; public class ProducerConsumerPattern {     public static void main(String args[]){      //Creating shared object      BlockingQueue sharedQueue = new LinkedBlockingQueue();      //Creating Producer and Consumer Thread      Thread prodThread = new Thread(new Producer(sharedQueue));      Thread consThread = new Thread(new Consumer(sharedQueue));      //Starting producer and Consumer thread      prodThread.start();      consThread.start();     } } //Producer Class in java class Producer implements Runnable {     private final BlockingQueue sharedQueue;     public Producer(BlockingQueue sharedQueue) {         this.sharedQueue = sharedQueue;     @Override     public void run() {         for(int i=0; i<10; i++){             try {                 System.out.println("Produced: " + i);                 sharedQueue.put(i);             } catch (InterruptedException ex) {                 Logger.getLogger(Producer.class.getName()).log(Level.SEVERE, null, ex);             }         } //Consumer Class in Java class Consumer implements Runnable{     public Consumer (BlockingQueue sharedQueue) {         while(true){                 System.out.println("Consumed: "+ sharedQueue.take());                 Logger.getLogger(Consumer.class.getName()).log(Level.SEVERE, null, ex); Output: Produced: 0 Produced: 1 Consumed: 0 Produced: 2 Consumed: 1 Produced: 3 Consumed: 2 Produced: 4 Consumed: 3 Produced: 5 Consumed: 4 Produced: 6 Consumed: 5 Produced: 7 Consumed: 6 Produced: 8 Consumed: 7 Produced: 9 Consumed: 8 Consumed: 9

你可以看到生産者線程生産數和消費者線程消費它以FIFO的順序,因為阻塞隊列隻允許元素以FIFO的方式來通路。以上就是使用阻塞隊列解決生産者消費者問題的全部,我确信它比wait/notify更簡單,但你要兩者都準備如果你是去面試話。