Java 多線程詳解(四)------生產者和消費者

  經過前面三篇博客的介紹,基本上對Java的多線程有了必定的瞭解了,而後這篇博客根據生產者和消費者的模型來介紹Java多線程的一些其餘知識。html

  咱們這裏的生產者和消費者模型爲:java

    生產者Producer 生產某個對象(共享資源),放在緩衝池中,而後消費者從緩衝池中取出這個對象。也就是生產者生產一個,消費者取出一個。這樣進行循環。程序員

 

  第一步:咱們先建立共享資源的類 Person,它有兩個方法,一個生產對象,一個消費對象多線程

public class Person {
	private String name;
	private int age;
	
	/**
	 * 生產數據
	 * @param name
	 * @param age
	 */
	public void push(String name,int age){
		this.name = name;
		this.age = age;
	}
	/**
	 * 取數據,消費數據
	 * @return
	 */
	public void pop(){
		System.out.println(this.name+"---"+this.age); 
	}
}

  第二步:建立生產者線程,並在 run() 方法中生產50個對象ide

public class Producer implements Runnable{
	//共享資源對象
	Person p = null;
	public Producer(Person p){
		this.p = p;
	}
	@Override
	public void run() {
		//生產對象
		for(int i = 0 ; i < 50 ; i++){
			//若是是偶數,那麼生產對象 Tom--11;若是是奇數,則生產對象 Marry--21
			if(i%2==0){
				p.push("Tom", 11);
			}else{
				p.push("Marry", 21);
			}
		}
	}
}

  第三步:建立消費者線程,並在 run() 方法中消費50個對象this

public class Consumer implements Runnable{
	//共享資源對象
	Person p = null;
	public Consumer(Person p) {
		this.p = p;
	}
	
	@Override
	public void run() {
		for(int i = 0 ; i < 50 ; i++){
			//消費對象
			p.pop();
		}
	}
}

  因爲咱們的模型是生產一個,立刻消費一個,那指望的結果即是 Tom---11,Marry--21,Tom---11,Mary---21......   連續這樣交替出現50次線程

可是結果倒是:htm

Marry---21
Marry---21
Marry---21
Marry---21
Marry---21
......
Marry---21
Marry---21
Marry---21
Marry---21
Marry---21

 

爲了讓結果產生的更加明顯,咱們在共享資源的 pop() 和 push() 方法中添加一段延時代碼對象

/**
	 * 生產數據
	 * @param name
	 * @param age
	 */
	public void push(String name,int age){
		this.name = name;
		try {
			//這段延時代碼的做用是可能只生產了 name,age爲nul,消費者就拿去消費了
			Thread.sleep(10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		this.age = age;
	}
	/**
	 * 取數據,消費數據
	 * @return
	 */
	public void pop(){
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(this.name+"---"+this.age); 
	}  

  

這個時候,結果以下:blog

Marry---11
Tom---21
Marry---11
Tom---21
Marry---11
Tom---21
Marry---11
Tom---21
......
Tom---11
Tom---21
Marry---11
Tom---21
Marry---11
Marry---21

  

結果分析:這時候咱們發現結果全亂套了,Marry--21是固定的,Tom--11是固定的,可是上面的結果所有亂了,那這又是爲何呢?並且有不少重複的數據連續出現,那這又是爲何呢?

緣由1:出現錯亂數據,是由於先生產出Tom--11,可是消費者沒有消費,而後生產者繼續生產出name爲Marry,可是age尚未生產,而消費者這個時候拿去消費了,那麼便出現 Marry--11。同理也會出現 Tom--21

緣由2:出現重複數據,是由於生產者生產一份數據了,消費者拿去消費了,可是第二次生產者生產數據了,可是消費者沒有去消費;而第三次生產者繼續生產數據,消費者纔開始消費,這便會產生重複

解決辦法1:生產者生產name和age必需要是一個總體一塊兒完成,即同步。生產的中間不能讓消費者來消費便可。便不會產生錯亂的數據。如何同步能夠參考:

        Java 多線程詳解(三)------線程的同步:http://www.cnblogs.com/ysocean/p/6883729.html

     這裏咱們選擇同步方法(在方法前面加上 synchronized)

public class Person {
	private String name;
	private int age;
	
	/**
	 * 生產數據
	 * @param name
	 * @param age
	 */
	public synchronized void push(String name,int age){
		this.name = name;
		try {
			//這段延時代碼的做用是可能只生產了 name,age爲nul,消費者就拿去消費了
			Thread.sleep(10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		this.age = age;
	}
	/**
	 * 取數據,消費數據
	 * @return
	 */
	public synchronized void pop(){
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(this.name+"---"+this.age); 
	}
}

  結果以下:

Marry---21
Marry---21
Marry---21
Marry---21
Marry---21
Tom---11
Tom---11
......
Tom---11
Tom---11
Tom---11
Tom---11
Tom---11

問題:仍是沒有解決上面的問題2,出現重複的問題。指望的結果是 Tom---11,Marry--21,Tom---11,Mary---21......   連續這樣交替出現50次。那如何解決呢?

解決辦法:生產者生產一次數據了,就暫停生產者線程,等待消費者消費;消費者消費完了,消費者線程暫停,等待生產者生產數據,這樣來進行。

 

 

這裏咱們介紹一個同步鎖池的概念:

  同步鎖池:同步鎖必須選擇多個線程共同的資源對象,而一個線程得到鎖的時候,別的線程都在同步鎖池等待獲取鎖;當那個線程釋放同步鎖了,其餘線程便開始由CPU調度分配鎖

 

關於讓線程等待和喚醒線程的方法,以下:(這是 Object 類中的方法)

  

 

  

 

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

notify():執行該方法的線程喚醒在等待池中等待的任意一個線程,把線程轉到鎖池中等待(注意鎖池和等待池的區別)

notifyAll():執行該方法的線程喚醒在等待池中等待的全部線程,把線程轉到鎖池中等待。

注意:上述方法只能被同步監聽鎖對象來調用,這也是爲啥wait() 和 notify()方法都在 Object 對象中,由於同步監聽鎖能夠是任意對象,只不過必須是須要同步線程的共同對象便可,不然別的對象調用會報錯:        java.lang.IllegalMonitorStateException

 

假設 A 線程和 B 線程同時操做一個 X 對象,A,B 線程能夠經過 X 對象的 wait() 和 notify() 方法來進行通訊,流程以下:

①、當線程 A 執行 X 對象的同步方法時,A 線程持有 X 對象的 鎖,B線程在 X 對象的鎖池中等待

②、A線程在同步方法中執行 X.wait() 方法時,A線程釋放 X 對象的鎖,進入 X 對象的等待池中

③、在 X 對象的鎖池中等待鎖的 B 線程得到 X 對象的鎖,執行 X 的另外一個同步方法

④、B 線程在同步方法中執行 X.notify() 方法,JVM 把 A 線程從等待池中移動到 X 對象的鎖池中,等待獲取鎖

⑤、B 線程執行完同步方法,釋放鎖,等待獲取鎖的 A 線程得到鎖,繼續執行同步方法

 

那麼爲了解決上面重複的問題,修改代碼以下:

public class Person {
	private String name;
	private int age;
	
	//表示共享資源對象是否爲空,若是爲 true,表示須要生產,若是爲 false,則有數據了,不要生產
	private boolean isEmpty = true;
	/**
	 * 生產數據
	 * @param name
	 * @param age
	 */
	public synchronized void push(String name,int age){
		try {
			//不能用 if,由於可能有多個線程
			while(!isEmpty){//進入到while語句內,說明 isEmpty==false,那麼表示有數據了,不能生產,必需要等待消費者消費
				this.wait();//致使當前線程等待,進入等待池中,只能被其餘線程喚醒
			}
			
			//-------生產數據開始-------
			this.name = name;
			//延時代碼
			Thread.sleep(10);
			this.age = age;
			//-------生產數據結束-------
			isEmpty = false;//設置 isEmpty 爲 false,表示已經有數據了
			this.notifyAll();//生產完畢,喚醒全部消費者
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}
	/**
	 * 取數據,消費數據
	 * @return
	 */
	public synchronized void pop(){
		try {
			//不能用 if,由於可能有多個線程
			while(isEmpty){//進入 while 代碼塊,表示 isEmpty==true,表示爲空,等待生產者生產數據,消費者要進入等待池中
				this.wait();//消費者線程等待
			}
			//-------消費開始-------
			Thread.sleep(10);
			System.out.println(this.name+"---"+this.age); 
			//-------消費結束------
			isEmpty = true;//設置 isEmpty爲true,表示須要生產者生產對象
			this.notifyAll();//消費完畢,喚醒全部生產者
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

  

結果:

Tom---11
Marry---21
Tom---11
Marry---21
Tom---11
Marry---21
Tom---11
......
Marry---21
Tom---11
Marry---21
Tom---11
Marry---21
Tom---11
Marry---21  

 

那麼這即是咱們期待的結果,交替出現。

 

 

死鎖:

①、多線程通訊的時候,很容易形成死鎖,死鎖沒法解決,只能避免

②、當 A 線程等待由 B 線程持有的鎖,而 B 線程正在等待由 A 線程持有的鎖時發生死鎖現象(好比A拿着鉛筆,B拿着圓珠筆,A說你先給我圓珠筆,我就把鉛筆給你,而B說你先給我鉛筆,我就把圓珠筆給你,這就形成了死鎖,A和B永遠不能進行交換)

③、JVM 既不檢測也不避免這種現象,因此程序員必須保證不能出現這樣的狀況

 

Thread 類中容易形成死鎖的方法(這兩個方法都已通過時了,不建議使用):

suspend():使正在運行的線程放棄 CPU,暫停運行(不釋放鎖)

resume():使暫停的線程恢復運行

情景:A 線程得到對象鎖,正在執行一個同步方法,若是 B線程調用 A 線程的 suspend() 方法,此時A 暫停運行,放棄 CPU 資源,可是不放棄同步鎖,那麼B也不能得到鎖,A又暫停,那麼便形成死鎖。

 

解決死鎖法則:當多個線程須要訪問 共同的資源A,B,C時,必須保證每個線程按照必定的順序去訪問,好比都先訪問A,而後B,最後C。就像咱們這裏的生產者---消費者模型,制定了必須生產者先生產一個對象,而後消費者去消費,消費完畢,生產者才能在開始生產,而後消費者在消費。這樣的順序便不會形成死鎖。

相關文章
相關標籤/搜索