【Java】【多執行緒】兩個執行緒間的通訊、wait、notify、notifyAll

love_Aym發表於2018-04-17

等待喚醒機制:wait()、notify()

1、什麼時候需要通訊

  • 多個執行緒併發執行時, 在預設情況下CPU是隨機切換執行緒的
  • 如果我們希望他們有規律的執行, 就可以使用通訊, 例如每個執行緒執行一次列印,輪流執行

2、怎麼通訊

  • 如果希望執行緒等待, 就呼叫wait()
  • 如果希望喚醒等待的執行緒, 就呼叫notify();
  • 這兩個方法必須在同步程式碼中執行, 並且使用同步鎖物件來呼叫,this
public class Demo1_Notify {
	/**
	 * @param args
	 * 等待喚醒機制
	 */
	public static void main(String[] args) {
		final Printer p = new Printer();
		
		new Thread() {       //執行緒1
			public void run() {
				while(true) {
					try {
						p.print1();
					} catch (InterruptedException e) {
						
						e.printStackTrace();
					}
				}
			}
		}.start();
		
		new Thread() {   //執行緒2
			public void run() {
				while(true) {
					try {
						p.print2();
					} catch (InterruptedException e) {
						
						e.printStackTrace();
					}
				}
			}
		}.start();
	}
}

//等待喚醒機制
class Printer {
	private int flag = 1;
	public void print1() throws InterruptedException {							
		synchronized(this) {
			if(flag != 1) {
				this.wait();	//當前執行緒等待,直到被喚醒
			}
			System.out.print("黑");
			System.out.print("馬");
			System.out.print("程");
			System.out.print("序");
			System.out.print("員");
			System.out.print("\r\n");
			flag = 2;
			this.notify();		//隨機喚醒單個等待的執行緒,此時仍然具有執行權,上面使得執行緒進入等待
		}
	}
	
	public void print2() throws InterruptedException {
		synchronized(this) {
			if(flag != 2) {
				this.wait();
			}
			System.out.print("傳");
			System.out.print("智");
			System.out.print("播");
			System.out.print("客");
			System.out.print("\r\n");
			flag = 1;
			this.notify();
		}
	}
}

2、多個執行緒通訊的問題

  • notify()方法是隨機喚醒一個執行緒
  • notifyAll()方法是喚醒所有執行緒
  • JDK5之前無法喚醒指定的一個執行緒
  • 如果多個執行緒之間通訊, 需要使用notifyAll()通知所有執行緒, 用while來反覆判斷條件
public class Demo2_NotifyAll {
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		final Printer2 p = new Printer2();
		new Thread() {
			public void run() {
				while(true) {
					try {
						p.print1();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}.start();
		
		new Thread() {
			public void run() {
				while(true) {
					try {
						p.print2();
					} catch (InterruptedException e) {	
						e.printStackTrace();
					}
				}
			}
		}.start();
		
		new Thread() {
			public void run() {
				while(true) {
					try {
						p.print3();
					} catch (InterruptedException e) {						
						e.printStackTrace();
					}
				}
			}
		}.start();
	}
}

class Printer2 {
	private int flag = 1;
	public void print1() throws InterruptedException {							
		synchronized(this) {
			while(flag != 1) {
				this.wait();	//當前執行緒等待
			}
			System.out.print("黑");
			System.out.print("馬");
			System.out.print("程");
			System.out.print("序");
			System.out.print("員");
			System.out.print("\r\n");
			flag = 2;
			//this.notify();	//隨機喚醒單個等待的執行緒
			this.notifyAll();
		}
	}
	
	public void print2() throws InterruptedException {
		synchronized(this) {
			while(flag != 2) {
				this.wait();	//執行緒2在此等待
			}
			System.out.print("傳");
			System.out.print("智");
			System.out.print("播");
			System.out.print("客");
			System.out.print("\r\n");
			flag = 3;
			//this.notify();
			this.notifyAll();
		}
	}
	
	public void print3() throws InterruptedException {
		synchronized(this) {
			while(flag != 3) {
				this.wait();	//執行緒3在此等待,if語句是在哪裡等待,就在哪裡起來
						//while迴圈是迴圈判斷,每次都會判斷標記
			}
			System.out.print("i");
			System.out.print("t");
			System.out.print("h");
			System.out.print("e");
			System.out.print("i");
			System.out.print("m");
			System.out.print("a");
			System.out.print("\r\n");
			flag = 1;
			//this.notify();
			this.notifyAll();
		}
	}
}

三、wait()和notify()的通常用法

Java多執行緒開發中,我們常用到wait()和notify()方法來實現執行緒間的協作,簡單的說步驟如下: 
1. A執行緒取得鎖,執行wait(),釋放鎖; 
2. B執行緒取得鎖,完成業務後執行notify(),再釋放鎖; 
3. B執行緒釋放鎖之後,A執行緒取得鎖,繼續執行wait()之後的程式碼;

注意:wait(long timeout):讓當前執行緒處於“等待(阻塞)狀態”,“直到其他執行緒呼叫此物件的notify()方法或 notifyAll() 方法,或者超過指定的時間量”,當前執行緒被喚醒(進入“就緒狀態”)。


四、通訊需要注意的幾點:

1、在同步程式碼塊中,用哪個物件鎖,就用哪個物件呼叫wait方法:this.wait()

2、為什麼wait方法和notify方法定義在Object這類中?

因為鎖物件可以是任意物件,Object是所有的類的基類,所以wait方法和notify方法需要定義在Object這個類中



相關文章