工具與資源中心
幫助開發者更加高效的工作,提供圍繞開發者全生命週期的工具與資源
developer.aliyun.com/tool?spm=a1z3...
一、前言
在上一篇 Java中的設計模式(一):觀察者模式 中我們瞭解了 觀察者模式 的基本原理和使用場景,在今天的這篇文章中我們要做一點簡單的延伸性學習——對比一下 生產者-消費者模式 和 *觀察者模式* ****的異同。
二、什麼是“生產者-消費者模式”?
和觀察者模式不同,生產者-消費者模式 本身並不屬於設計模式中的任何一種 。那麼生產者-消費者模式到底是什麼呢?下面我們用一個例子簡單說明一下:
如同上圖中所示,生產者和消費者就如同一本雜誌的投稿作者和訂閱的讀者,同一本雜誌的投稿作者可以有多個,它的讀者也可以有多個,而雜誌就是連線作者和讀者的橋樑(即緩衝區)。通過雜誌這個資料緩衝區,作者可以將完成的作品投遞給訂閱了雜誌的讀者,在這一過程中,作者不用關心讀者是否收到了作品或是否完成了閱讀,作者和讀者是兩個相對獨立的物件,兩者的行為互不影響。
可以看到,在這個例子當中出現了三個角色,分別是 生產者 、 消費者 以及 緩衝區 。生產者和消費者比較好理解,前者是生產資料,後者則是處理前者生產出來的資料。而緩衝區在生產者-消費者模式中則起到了一個 解耦 、 支援非同步 、 支援忙閒不均 的作用。
三、兩者的區別
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 協議》,轉載必須註明作者和本文連結