1. 基礎知識:
1. 什麼是生產者-消費者模式:
比如有兩個進程A和B,它們共享一個固定大小的緩衝區,A進程產生數據放入緩衝區,B進程從緩衝區中取出數據進行計算,那麼這裏其實就是一個生產者和消費者的模式,A相當於生產者,B相當於消費者。
2. 爲什麼要使用生產者消費者模式:
在多線程開發中,如果生產者生產數據的速度很快,而消費者消費數據的速度很慢,那麼生產者就必須等待消費者消費完了數據才能夠繼續生產數據,因爲生產那麼多也沒有地方放啊;同理如果消費者的速度大於生產者那麼消費者就會經常處理等待狀態,所以爲了達到生產者和消費者生產數據和消費數據之間的平衡,那麼就需要一個緩衝區用來存儲生產者生產的數據,所以就引入了生產者-消費者模式。
簡單來說這裏的緩衝區的作用就是爲了平衡生產者和消費者的處理能力,起到一個數據緩存的作用,同時也達到了一個解耦的作用。
3. 生產者-消費者模式的特點:
- 保證生產者不會在緩衝區滿的時候繼續向緩衝區放入數據,而消費者也不會在緩衝區空的時候,消耗數據;
- 當緩衝區滿的時候,生產者會進入休眠狀態,當下次消費者開始消耗緩衝區的數據時,生產者纔會被喚醒,開始往緩衝區中添加數據;當緩衝區空的時候,消費者也會進入休眠狀態,直到生產者往緩衝區中添加數據時纔會被喚醒;
2. 問題要點:
其實生產者消費者模型挺像觀察者模式的,對於該模型我們應該明確以下4點:
1. 當生產者生產出產品時,應該通知消費者去消費。
2. 當消費者消費完產品,應該通知生產者去生產。
3. 當產品的庫存滿了的時候,生產者不應該再去生產,而是通知消費者去消費。
4. 當產品的庫存爲0的時候,消費者不應該去消費,而是通知生產者去生產。
生產者和消費者問題是線程模型中的經典問題:生產者和消費者在同一時間段內共用同一個存儲空間,生產者往存儲空間中添加產品,消費者從存儲空間中取走產品,當存儲空間爲空時,消費者阻塞,當存儲空間滿時,生產者阻塞。
3. 代碼實現:wait()和notify()實現
利用內部線程之間的通信:Object的wait() / notify()方法來實現生產者-消費者模型。
ps:採用wait()/notify()方法的缺點是不能實現單生產者單消費者模式,因爲要是用notify()就必須使用同步代碼塊。
1. 定義生產者。
Producter.java
package demo;
public class Producter implements Runnable {
private Storage resource;
public Producter(Storage resource) {
super();
this.resource = resource;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
resource.produce();
}
}
}
2. 定義消費者。
Consumer.java
package demo;
public class Consumer implements Runnable {
private Storage resource;
public Consumer(Storage resource) {
super();
this.resource = resource;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
resource.cosume();
}
}
}
3. 定義Storage倉庫。
Storage.java
package demo;
public class Storage {
private int MAX_SIZE = 20;
private int count = 0;
public synchronized void cosume() {
while (count == 0) {
try {
System.out.println("【消費者】庫存已經爲空了,暫時不能進行消費任務!");
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count--;
System.out.println("【消費者】" + Thread.currentThread().getName() + "消費產品, 庫存:" + count);
this.notify();
}
public synchronized void produce() {
while (count >= MAX_SIZE) {
try {
System.out.println("【生產者】庫存已經滿了,暫時不能進行生產任務!");
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count++;
System.out.println("【生產者】" + Thread.currentThread().getName() + "生產產品, 庫存" + count);
this.notify();
}
}
4. 測試demo。
ProducterCosumerXDemo.java
package demo;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ProducterCosumerXDemo {
public static void main(String[] args) {
Storage storage = new Storage();
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
service.submit(new Producter(storage));
}
for (int i = 0; i < 5; i++) {
service.submit(new Consumer(storage));
}
service.shutdown();
}
}
4. 運行結果:
5. 分析:
我們這裏創建了5個生產者,5個消費者。生產者生產的速度比消費者消費的速度要快,從圖中很明顯看到生產者率先生產出20個產品,已是庫存極大值,往後不能再去生產了,然後通知消費者去消費。