生產者消費者模式

生產者消費者模式說明:

  1. 生產者只在倉庫未滿時進行生產,倉庫滿時生產者進程被阻塞;
  2. 消費者只在倉庫非空時進行消費,倉庫爲空時消費者進程被阻塞;

實現的關鍵:

共享內存中的兩個同步方法,及同步方法中wait()方法的調用。html

  • synchronized 保證了對象只能被一個線程佔用。
  • wait 保證了當線程在等待過程當中釋放鎖,使得其餘對象有機會得到鎖。

執行wait後,當前線程處於等待狀態,而且會釋放鎖。 執行notify後,當前線程並不會立刻釋放鎖,而是執行完runnable代碼後纔會釋放鎖。java

Java中有一個同步模型-監視器,負責管理線程對對象中的同步方法的訪問,它的原理是:賦予該對象惟一一把'鑰匙',當多個線程進入對象,只有取得該對象鑰匙的線程才能夠訪問同步方法,其它線程在該對象中等待,直到該線程用wait()方法放棄這把鑰匙,其它等待的線程搶佔該鑰匙,搶佔到鑰匙的線程後纔可得以執行,而沒有取得鑰匙的線程仍被阻塞在該對象中等待。 總而言之,synchonized使得只有一個線程能進入臨界代碼區。bash

因爲wait( )所等待的對象必須先鎖住,所以,它只能用在同步化程序段或者同步化方法內,不然,會拋出異常java.lang.IllegalMonitorStateException.多線程

代碼

  • 倉庫(緩衝區),用於管理產品的生產、消費和存儲
/**
 * 倉庫類,用於管理產品的生產、消費和存儲。
 */
public class Storage<T> {
	private int index = 0;
	private static final int MAX = 10;//最大容量
	private List<T> storages = new ArrayList<T>(MAX);//存儲集合

	public synchronized void produce(T item) {
		while (index >= MAX) {// 判斷倉庫滿了,則等待。
			try {
				System.out.println("倉庫滿了,等待中...");
				this.wait();
				System.out.println("倉庫不滿了,開始生產");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println("生產>>" + item.toString());
		storages.add(item);
		index++;   //先添加item,在進行加1操做
		notify();  //喚醒在此對象監視器上等待的單個線程,即消費者線程
	}

	public synchronized T consume() {
		while (index <= 0) {// 判斷倉庫滿了,則等待。
			try {
				System.out.println("倉庫爲空,等待中...");
				this.wait();
				System.out.println("倉庫不爲空,開始消費");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

		index--;//先進行減1操做,再remove
		T item = storages.remove(index);
		System.out.println("消費>>" + item.toString());
		notify();
		return item;
	}
}
複製代碼
  • 產品
public class Phone {

	private int id;// 手機編號

	public Phone(int id) {
		this.id = id;
	}

	@Override
	public String toString() {
		return "手機編號:" + id;
	}
}
複製代碼
  • 生產者
public class Producer implements Runnable {

	private Storage<Phone> storage;
	
	public Producer(Storage<Phone> storage) {
		this.storage = storage;
	}

	public void run() {
		for(int i = 0;i<20;i++){
			storage.produce(new Phone(i));
			
			try {
				Thread.sleep(10);//每隔10毫秒生產一個產品
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}
複製代碼
  • 消費者
public class Consumer implements Runnable {

	private Storage<Phone> storage;
	
	public Consumer(Storage<Phone> storage) {
		this.storage = storage;
	}

	public void run() {
		for(int i = 0;i<20;i++){
			storage.consume();
			try {
				Thread.sleep(100);//每隔100毫秒消費一個
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
複製代碼
  • 客戶端
public class ProducerAndConsumer {

	public static void main(String[] args) {
		Storage<Phone> storage = new Storage<Phone>();
		
		new Thread(new Producer(storage)).start();
		new Thread(new Consumer(storage)).start();
	}
}
複製代碼
  • 結果 生產>>手機編號:0 消費>>手機編號:0 生產>>手機編號:1 生產>>手機編號:2 生產>>手機編號:3 生產>>手機編號:4 生產>>手機編號:5 生產>>手機編號:6 生產>>手機編號:7 生產>>手機編號:8 生產>>手機編號:9 消費>>手機編號:9 生產>>手機編號:10 生產>>手機編號:11 倉庫滿了,等待中... 消費>>手機編號:11 倉庫不滿了,開始生產 生產>>手機編號:12 倉庫滿了,等待中... 消費>>手機編號:12 倉庫不滿了,開始生產 生產>>手機編號:13 倉庫滿了,等待中... 消費>>手機編號:13 倉庫不滿了,開始生產 生產>>手機編號:14 倉庫滿了,等待中... 消費>>手機編號:14 倉庫不滿了,開始生產 生產>>手機編號:15 倉庫滿了,等待中... 消費>>手機編號:15 倉庫不滿了,開始生產 生產>>手機編號:16 倉庫滿了,等待中... 消費>>手機編號:16 倉庫不滿了,開始生產 生產>>手機編號:17 倉庫滿了,等待中... 消費>>手機編號:17 倉庫不滿了,開始生產 生產>>手機編號:18 倉庫滿了,等待中... 消費>>手機編號:18 倉庫不滿了,開始生產 生產>>手機編號:19 消費>>手機編號:19 消費>>手機編號:10 消費>>手機編號:8 消費>>手機編號:7 消費>>手機編號:6 消費>>手機編號:5 消費>>手機編號:4 消費>>手機編號:3 消費>>手機編號:2 消費>>手機編號:1

2018.11.18補充 如上代碼知識模擬的一個生產者一個消費者的場景,並且生產者和消費者分別執行20次。徹底可使用while(true)使得一直生產和消費。 對於一個生產者和一個消費者的場景,可使用if來判斷臨界值。並不須要while(xxx)作處理。 而對於多生產者或多消費則須要使用while處理,同時須要使用notifyAll去喚醒。不然可能出現假死的狀況,即生產者和消費者都處於wait,所以notify不單單能夠喚醒異類,也能夠喚醒同類(即生產者也能夠喚醒另外一個生產者)ide

參閱

生產者消費者模式詳解及代碼實現 生產者/消費者問題的多種Java實現方式 Java多線程之生產者消費者經典問題ui

相關文章
相關標籤/搜索