Java中的設計模式(二):生產者-消費者模式與觀察者模式

hjavn發表於2021-08-26

工具與資源中心

幫助開發者更加高效的工作,提供圍繞開發者全生命週期的工具與資源
developer.aliyun.com/tool?spm=a1z3...

一、前言

  在上一篇 Java中的設計模式(一):觀察者模式 中我們瞭解了 觀察者模式 的基本原理和使用場景,在今天的這篇文章中我們要做一點簡單的延伸性學習——對比一下 生產者-消費者模式 和 *觀察者模式* ****的異同。

二、什麼是“生產者-消費者模式”?

  和觀察者模式不同,生產者-消費者模式 本身並不屬於設計模式中的任何一種 。那麼生產者-消費者模式到底是什麼呢?下面我們用一個例子簡單說明一下:

生產者-消費者模式.jpg

  如同上圖中所示,生產者和消費者就如同一本雜誌的投稿作者和訂閱的讀者,同一本雜誌的投稿作者可以有多個,它的讀者也可以有多個,而雜誌就是連線作者和讀者的橋樑(即緩衝區)。通過雜誌這個資料緩衝區,作者可以將完成的作品投遞給訂閱了雜誌的讀者,在這一過程中,作者不用關心讀者是否收到了作品或是否完成了閱讀,作者和讀者是兩個相對獨立的物件,兩者的行為互不影響。

  可以看到,在這個例子當中出現了三個角色,分別是 生產者消費者 以及 緩衝區 。生產者和消費者比較好理解,前者是生產資料,後者則是處理前者生產出來的資料。而緩衝區在生產者-消費者模式中則起到了一個 解耦支援非同步支援忙閒不均 的作用。

三、兩者的區別

1. 程式設計正規化不同

  生產者-消費者模式和觀察者模式的第一個不同點在上面已經說過,前者是一種 程式導向 的軟體設計模式,不屬於Gang of Four提出的23種設計模式中的任何一種,而後者則是23中設計模式中的一種,也即物件導向的設計模式中的一種。

2. 關聯關係不同

  這一理念上的不同就帶出了下一種不同點,即觀察者模式中只有一對多的關係,沒有多對多的關係,而在生產者-消費者模式中則是多對多的關係。

  在觀察者模式中,被觀察者只有一個,觀察者卻可以有多個。就比如十字路口的交通燈,直行的車輛只會觀察控制直行的交通燈,不會去觀察控制左拐或者右拐的交通燈,也就是說觀察的物件是固定唯一的。

  而在生產者-消費者模式中則不同,生產者可以有多個,消費者也可以有多個。還是用上面作者和讀者的例子,在這個例子當中,讀者只關心雜誌的內容而不必關心內容的創作者是誰,作者也只需要知道創作完的作品可以釋出到對應的雜誌,而不必關心會有那些讀者。

3. 耦合關係不同

  從上一個不同中不難看出生產者-消費者模式和觀察者模式的耦合關係也不相同,前者為 輕耦合 ,後者為 重耦合 。

4. 應用場景不同

  觀察者模式多用於 事件驅動模型 當中,生產者-消費者模式則多出現在 程式間通訊 ,用於進行解耦和併發處理,我們常用的訊息佇列用的就是生產者-消費者模式。當然在Java中使用生產者-消費者模式還需要注意緩衝區的執行緒安全問題,這裡就不做過多敘述。

四、一個小例子

  最後用一個簡單的demo來結束本次的延伸學習。

1. StoreQueue–緩衝區

public class StoreQueue<T> {  
private final BlockingQueue<T> queue = new LinkedBlockingQueue<>();  
/**  
* 佇列中增加資料  
*  
* @param data 生產者生產的資料  
*/  
public void add(T data) {  
try { 
queue.put(data);  
} catch (Exception e) {
e.printStackTrace();  
}  
}  
/** 
* 佇列中獲取資料  
*  
* @return 從佇列中獲取到的資料  
*/ 
public T get() {  
try {  
return queue.take();  
} catch (Exception e) { 
e.printStackTrace();  
}  
return null;  
} 
}

  在這個例子中,我們使用了jdk自身的 阻塞佇列BlockingQueue 來實現了一個緩衝區,這裡只需要實現放資料和取資料的方法。如果我們自己實現一個阻塞佇列,一方面需要注意阻塞的處理,另一方面需要考慮執行緒安全的問題,這裡就不展開敘述了,有興趣的同學可以看下BlockingQueue的原始碼。

2. Producer–生產者

public class Producer implements Runnable{  
private StoreQueue<String> storeQueue;  
public Producer(StoreQueue<String> storeQueue) {  
this.storeQueue = storeQueue;  
} 
@Override  
public void run() {  
for (int i = 0; i < 10; i++) {  
storeQueue.add(Thread.currentThread().getName() + ":" + i);  
}  
} 
}

3. Consumer–消費者

public class Consumer implements Runnable{  
private StoreQueue<String> storeQueue;  
public Consumer(StoreQueue<String> storeQueue) {  
this.storeQueue = storeQueue; 
}  
@Override 
public void run() {  
try {  
while (true) {  
String data = storeQueue.get(); 
System.out.println("當前消費執行緒 : " + Thread.currentThread().getName() + ", 接收到資料 : " + data);  
}  
} catch (Exception e) {  
e.printStackTrace();  
Thread.currentThread().interrupt();  
}  
} 
}

4. 執行邏輯和執行結果

執行邏輯

public static void main(String[] args) {  
StoreQueue<String> storeQueue = new StoreQueue<>();  
Producer producer = new Producer(storeQueue);  
Consumer consumer = new Consumer(storeQueue);  
Producer producerTwo = new Producer(storeQueue);  
Consumer consumerTwo = new Consumer(storeQueue);  
new Thread(producer).start();  
new Thread(consumer).start();  
new Thread(producerTwo).start();  
new Thread(consumerTwo).start();  
}

執行結果

當前消費執行緒 : Thread-1, 接收到資料 : Thread-0:0 
當前消費執行緒 : Thread-1, 接收到資料 : Thread-0:1 
當前消費執行緒 : Thread-1, 接收到資料 : Thread-0:2 
當前消費執行緒 : Thread-1, 接收到資料 : Thread-0:3 
當前消費執行緒 : Thread-1, 接收到資料 : Thread-0:4 
當前消費執行緒 : Thread-3, 接收到資料 : Thread-0:5 
當前消費執行緒 : Thread-3, 接收到資料 : Thread-0:7 
當前消費執行緒 : Thread-3, 接收到資料 : Thread-0:8 
當前消費執行緒 : Thread-3, 接收到資料 : Thread-0:9 
當前消費執行緒 : Thread-3, 接收到資料 : Thread-2:0 
當前消費執行緒 : Thread-3, 接收到資料 : Thread-2:1 
當前消費執行緒 : Thread-3, 接收到資料 : Thread-2:2 
當前消費執行緒 : Thread-3, 接收到資料 : Thread-2:3 
當前消費執行緒 : Thread-3, 接收到資料 : Thread-2:4 
當前消費執行緒 : Thread-3, 接收到資料 : Thread-2:5 
當前消費執行緒 : Thread-3, 接收到資料 : Thread-2:6 
當前消費執行緒 : Thread-3, 接收到資料 : Thread-2:7 
當前消費執行緒 : Thread-3, 接收到資料 : Thread-2:8 
當前消費執行緒 : Thread-3, 接收到資料 : Thread-2:9 
當前消費執行緒 : Thread-1, 接收到資料 : Thread-0:6

  可以看到在上面的資料結果中,不同生產者生產的資料只會被一個消費者消費,沒有出現執行緒安全問題,這要歸功於實現緩衝區使用到的 BlockingQueue

本文轉自:developer.aliyun.com/article/78694...

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章