1. 程式人生 > >執行緒通訊-等待和喚醒機制和鎖(Lock)機制

執行緒通訊-等待和喚醒機制和鎖(Lock)機制

生產者和消費者案例分析:

經典的生產者和消費者案例(Producer/Consumer):

     分析案例:

              1):生產者和消費者應該操作共享的資源(實現方式來做).

              2):使用一個或多個執行緒來表示生產者(Producer).

              3):使用一個或多個執行緒來表示消費者(Consumer).

生產者消費者的示意圖:


為什麼生產者不直接把資料給消費者,而是先把資料儲存到共享資源中,然後,消費者再從共享資源中取出資料,再消費.

在這裡體現了面向物件的設計理念:低耦合.

       ()耦合: 直接使用生產者把肉包子給消費者

,那麼生產者中得存在消費者的引用,同理,消費者要消費生產者生產的肉包子,消費者中也得存在生產者物件的引用. 例子: 主機板和整合顯示卡.

       ()耦合:使用一箇中間物件,遮蔽了生產者和消費者直接的資料互動.  例子:主機板和獨立顯示卡.

()耦合:

//生產者
public class  Producer{
     private  Consumer  con;//消費者物件
}

//消費者
public  class    Consumer{
        private  Producer  pro;//消費者物件
}

()耦合:

//共享資源
public  class ShareResource{
}

//生產者
public class  Producer{
       private  ShareResource  resource;//共享資源物件
}

//消費者
public  class    Consumer{
         private  ShareResource  resource;//共享資源物件
}

實現生產者和消費者案例

生產者:

//生產者生產資料
public class Producer implements Runnable{
	
	private ShareResource resource = null;
	
	public Producer(ShareResource resource){
		this.resource = resource;
	}

	public void run() {
		for(int i = 0; i < 50; i++){
			if(i % 2 == 0){
				resource.push("春哥", "男");
			}
			else{
				resource.push("鳳姐", "女");
			}
		}
	}
}

消費者:

//消費者消費類
public class Consumer implements Runnable{
	
	private ShareResource resource = null;
	
	public Consumer(ShareResource resource){
		this.resource = resource;
	}

	
	public void run() {
		for(int i = 0; i < 50; i ++){
			resource.popup();
		}
	}
}

共享資源類:

//生產者和消費者共同的資源物件
public class ShareResource {
	private String name;
	private String gender; //一般用列舉來寫性別,這裡先用String	
	
	/**
	 * 生產者向共享資源儲存資料
	 * @param name 生產者生產的姓名
	 * @param gender 生產者生產的性別
	 */
	public void push(String name,String gender){
			this.name = name;
			Thread.sleep(10);
			this.gender = gender;
	}
	
	/**
	 * 消費者向共享資源獲取資料
	 */
	 public void popup(){
			System.out.println(this.name + "-" + this.gender);
	}
}

分析生產者和消費者案例存在的問題:

  建議在生產姓名和性別之間以及在列印之前使用Thread.sleep(10);使效果更明顯.

  此時出現下面的情況:


問題1:出現姓別紊亂,即出現鳳姐-男的情況.

      生產者先生產出春哥哥-,此時消費者沒有消費,生產者繼續生產出姓名為鳳姐,此時消費者開始消費了.

      解決方案:只要保證在生產姓名和性別的過程保持同步,中間不能被消費者執行緒進來取走資料.

      可以使用同步程式碼塊/同步方法/Lock機制來保持同步性.

問題2:重複出現鳳姐-男和鳳姐-女,應該出現生產一個數據,消費一個數據.

      應該交替出現:  春哥哥--->鳳姐--->春哥哥--->鳳姐-.....

      解決方案: 得使用 等待和喚醒機制.


執行緒通訊:不同的執行緒執行不同的任務,如果這些任務有某種關係,執行緒之間必須能夠通訊,協調完成工作.

等待和喚醒機制:

執行緒通訊-waitnotify方法介紹:

java.lang.Object類提供類兩類用於操作執行緒通訊的方法.

wait():執行該方法的執行緒物件釋放同步鎖,JVM把該執行緒存放到等待池中,等待其他的執行緒喚醒該執行緒.

notify:執行該方法的執行緒喚醒在等待池中等待的任意一個執行緒,把執行緒轉到鎖池中等待.

notifyAll():執行該方法的執行緒喚醒在等待池中等待的所有的執行緒,把執行緒轉到鎖池中等待.

注意:上述方法只能被同步監聽鎖物件來呼叫,否則報錯IllegalMonitorStateException..

------------------------------------------

假設A執行緒和B執行緒共同操作一個X物件(同步鎖),A,B執行緒可以通過X物件的waitnotify方法來進行通訊,流程如下:

1:A執行緒執行X物件的同步方法時,A執行緒持有X物件的鎖,B執行緒沒有執行機會,B執行緒在X物件的鎖池中等待.

2:A執行緒在同步方法中執行X.wait()方法時,A執行緒釋放X物件的鎖,進入A執行緒進入X物件的等待池中.

3:X物件的鎖池中等待鎖的B執行緒獲取X物件的鎖,執行X的另一個同步方法.

4:B執行緒在同步方法中執行X.notify()方法時,JVMA執行緒從X物件的等待池中移動到X物件的鎖池中,等待獲取鎖.

5:B執行緒執行完同步方法,釋放鎖.A執行緒獲得鎖,繼續執行同步方法.

因此,生產者消費者問題中的共享資源物件類可改為:

//生產者和消費者共同的資源物件
public class ShareResource {
	private String name;
	private String gender;  //一般用列舉來寫性別,這裡先用String
	private boolean isEmpty = true;
	
	
	/**
	 * 生產者向共享資源儲存資料
	 * @param name 生產者生產的姓名
	 * @param gender 生產者生產的性別
	 */
	synchronized public void push(String name,String gender){
		
		
		try {
			while(!isEmpty){//isEmpty為false時,當前執行緒等待
			//if(!isEmpty)用if也可以達到效果,只是while更安全(至少判斷兩次)				
				this.wait();
			}
			this.name = name;
			Thread.sleep(10);
			this.gender = gender;
			
			isEmpty = false;//設定共享資源不為空
			this.notify();  //喚醒其他任意一個執行緒,這裡是喚醒一個消費者
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
	}
	
	/**
	 * 消費者向共享資源獲取資料
	 */
	 synchronized public void popup(){
		try {
			while(isEmpty){//當isEmpty為true時,共享資源為空,消費者等待
				this.wait();
			Thread.sleep(10);
			}
			System.out.println(this.name + "-" + this.gender);
			isEmpty = true;//消費結束,資源應設為空
			this.notify();// 喚醒一個生產者
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}

}

除了等待喚醒機制,也可以使用鎖機制

執行緒通訊-使用LockCondition介面:

waitnotify方法,只能被同步監聽鎖物件來呼叫,否則報錯IllegalMonitorStateException.

那麼現在問題來了,Lock機制根本就沒有同步鎖了,也就沒有自動獲取鎖和自動釋放鎖的概念.

因為沒有同步鎖,所以Lock機制不能呼叫waitnotify方法.

解決方案:Java5中提供了Lock機制的同時提供了處理Lock機制的通訊控制的Condition介面.

--------------------------------------------------------------------

Java5開始,可以:

      1):使用Lock機制取代synchronized 程式碼塊和synchronized 方法.

      2):使用Condition介面物件的await,signal,signalAll方法取代Object類中的wait,notify,notifyAll方法.


共享資源類可改成:
//生產者和消費者共同的資源物件
public class ShareResource {
	private String name;
	private String gender;
	private boolean isEmpty = true;
	private final Lock lock = new ReentrantLock();
	private final Condition condition = lock.newCondition();
	
	/**
	 * 生產者向共享資源提供資料
	 * @param name 生產者生產的名字
	 * @param gender 性別
	 */
	public void push(String name,String gender){
		lock.lock();
		try {
			if(!isEmpty){
				condition.await();
			}
			this.name = name;
			Thread.sleep(10);
			this.gender = gender;
			isEmpty = false;
			condition.signal();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
		
	}
	
	/**
	 * 消費者從共享資源獲取(消費)資料
	 */
	public void popoup(){
		lock.lock();
		try {
			if(isEmpty){
				condition.await();
			}
			Thread.sleep(10);
			System.out.println(name+"-"+gender);
			isEmpty = true;
			condition.signal();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
		
	}
}