生產者和消費者模型


生產者和消費者模型

線程通信:不同的線程執行不同的任務,如果這些任務有某種關系,各個線程必須要能夠通信,從而完成工作。線程通信中的經典問題:生產者和消費者問題

模型:

這個模型也體現了面向對象的設計理念:低耦合

也就是為什么生產者生產的東西為什么不直接給消費者,還有經過一個緩沖區(共享資源區)

這就相當於去包子店吃包子,你要5個包子,老板把5個人包子放在一個盤子中再給你,這個盤子就是一個緩沖區。

現在模擬一個生產者和消費者模型

  • 生產者生產“李小龍 男”,消費者消費也就是打印出“李小龍 男”

  • 生產者生產“狗晗 女”,消費者消費也就是打印出“狗晗 女”

  • 要求生產者生產一個,消費者消費一個。也就是緩沖區為1

  • 期望結果:

    李小龍 男

    狗晗 女

    李小龍 男

    狗晗 女

    ···

解決問題

需要創建4個類,分別是生產者,消費者,共享資源類,測試類

  • 生產者類實現Runnable接口,覆蓋run方法

    public class Producer implements Runnable{
    	ShareResource resource = null;
    	public Producer(ShareResource resource){
    		this.resource = resource;
    	}
    	@Override
    	public void run() {
    		for(int i = 0; i < 50;i++){
    			if(i % 2 == 0){
    				resource.product("李小龍", "男");
    			}else{
    				resource.product("狗晗","女");
    			}
    		}
    	}
    	
    }
    
  • 消費者類實現Runnable接口,覆蓋run方法

    public class Consumer implements Runnable {
    	ShareResource resource = null;
    	public Consumer(ShareResource resource){
    		this.resource = resource;
    	}
    	public void run(){
    		for(int i = 0;i < 50;i++){
    			resource.consume();
    		}
    	}
    }
    
  • 共享資源類有生產者和消費者兩個類的引用

    public class ShareResource {
    	private String name;
    	private String sex;
    	
    	public void product(String name,String sex){
    	    this.name = name;
            try{
                Thread.sleep(10);
            }catch(Exception e){
                e.printStackTrace();
            }
    		this.sex = sex;
    	}
    	
    	public void consume(){
            try{
                Thread.sleep(10);
            }catch(Exception e){
                e.printStackTrace();
            }
    		System.out.println(this.name + " " + this.sex);
    	}
    }
    
  • 測試類可以啟動線程

    public class Demo {
    	public static void main(String[]args){
    		ShareResource resource = new ShareResource();
    		new Thread(new Producer(resource)).start();
    		new Thread(new Consumer(resource)).start();
    	}
    }
    

運行后發現,結果性別發生了紊亂:

我靠,這我龍哥願意嗎,怎么回事?

就是因為多線程並發同一資源,要把它們的方法用synchronized修飾保證它們生產和消費不同時進行,當你加上synchronized之后:

確實性別紊亂的問題是解決了,狗晗是高興了,但是它們並不是交替出現的啊,怎么回事?

你要交替出現,必須一個等着一個啊,你生產完了,你得停下來休息啊,讓消費者先消費,等到消費者消費完之后,讓消費者把你叫醒再繼續生產,消費者就又去休息去了,這個消費者優點類似於吃了睡,睡了在吃一樣。

wait和notify方法

  • wait方法:執行該方法的對象釋放同步鎖,JVM把該線程存放到等待池中,等待其他線程喚醒

  • notify方法:喚醒在等待池中的等待的任意一個線程,把線程轉移到鎖池

  • notifyAll方法:喚醒在等待池中等待的所有線程,把線程轉移到鎖池

    等待池:沒有機會獲取同步鎖,只能等待被喚醒,被轉移到鎖池中。

    鎖池: 當前生產者在生產數據的時候(先擁有同步鎖),其他線程就在鎖池中等待獲取鎖.,當線程執行完同步代碼塊的時候,就會釋放同步鎖,其他線程開始搶鎖的使用權.

又來看這個模型,應該得定義一個判斷當前共享資源區是否為空,如果為空,消費者就應該等待生產者生產,當生產者生產 完畢后應該喚醒消費者進行消費。如果不為空,生產者就應該等待消費者,等消費者完畢后再喚醒生產者繼續生產

public class ShareResource {
	private String name;
	private String sex;
	private boolean isEmpty = true;//判斷共享區空和滿的一個標志
	//生產者生產
	synchronized public void product(String name,String sex){
		try {
			while(!isEmpty){//共享區不空,生產者需要停下等待消費者消費
				/**
				 * this是同步鎖(同步監聽對象),wait方法是Object類中 的方法,調用該方法就釋放同步鎖
				 * 然后JVM把該線程存放到等待池中,等待其他線程喚醒該線程
				 * 該方法只能被同步監聽對象調用,否則報錯IllegalMonitorStateException.
				 */
				
				this.wait();
			}
			//----------開始生產-----------
			this.name = name;
			Thread.sleep(10);
			this.sex = sex;
			//---------結束生產------------
			isEmpty = false;  //設置共享區為不空
			/**
			 * notify方法是喚醒等待池中的隨機一個線程,把線程轉移到鎖池中等待(鎖池中有機會獲取到鎖)
			 * notifyAll方法是喚醒等待池中的所有線程,把線程轉移到鎖池中等待
			 * 該方法只能被同步監聽對象鎖調用,否則報錯
			 */
			this.notify();	//喚醒一個消費者               notify是喚醒其中一個,notifyAll是喚醒全部
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	//消費者消費
	synchronized public void consume(){
		try {
			while(isEmpty){//當共享區為空時,消費者進行等待
				this.wait();
			}
			//------------開始消費-------------
			Thread.sleep(30);
			System.out.println(name + " " + sex);
			//------------結束消費-------------
			isEmpty = true;
			this.notify();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	} 
}

用synchronized修飾方法,會自動獲取鎖,自動釋放鎖。從java5開始使用Lock機制取代synchronized代碼塊和synchronized方法,使用Condition接口對象的await,signal,signalAll方法取代notify,notifyAll,用法還是大同小異

public class ShareResource {
	private String name;
	private String sex;
	private boolean isEmpty = true;
	private final Lock lock = new ReentrantLock();
	private Condition condition = lock.newCondition();
	
	public void product(String name,String sex){
		lock.lock();//獲取鎖
		try {
			while(!isEmpty){//不空,生產者要等待消費者消費
				condition.await();//等待,相當於this.wait();
			}
			this.name = name;
			Thread.sleep(10);
			this.sex = sex;
			
			isEmpty = false;//把共享區置為不空
			condition.signal();//喚醒一個線程,相當於this.notify().  signalAll 可以喚醒全部線程
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally{
			lock.unlock();//釋放鎖
		}
	}
	
	public void consume(){
		lock.lock();
		try {
			while(isEmpty){//共享區為空,需要等待生產者生產
				condition.await();
			}
			
			Thread.sleep(20);
			System.out.println(this.name + " " + this.sex);
			
			isEmpty = false;
			condition.signal();
			
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
}

結果就是:

Lock機制

創建一個Lock接口實現類ReetranLock的對象,進入同步方法后立即加鎖,lock.lock();最后釋放鎖,看API中的典型代碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM