基礎篇:wait與notify與notifyAll(八)

這篇文章主要講解多線程編程中 wait與notify與notifyAll 這三個方法的運用,瞭解了它們的基本用法後,我們再寫個 “積累能量---放大招”  的例子來整合演示這幾個方法的協作運用;


見名之意,wait是等待的意思,當在線程A內調用wait()時,線程A將暫停運行,直到其它的線程通過調用notify或者notifyAll方法喚醒它爲止;

這三個方法都是從Object類繼承而來的,它們必須運行在持鎖的代碼中,否則將拋出異常,想一想,爲什麼這些方法會定義在Object中,又爲什麼必須運行在持鎖的情況下呢?答案提示:想想同步的意義...



wait與notifyAll的用法:

我們先下一個結論:wait是會釋放鎖的,而sleep是不會釋放鎖的!接下來我們來看代碼:

class TempObj{
	
	//用來證明sleep是不會釋放鎖的
	public synchronized void bySleep(){
		System.out.println("tempObj.bySleep()");
		try {
			TimeUnit.MILLISECONDS.sleep(20000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	//用來證明wait是會釋放鎖的
	public synchronized void byWait(){
		System.out.println("tempObj.byWait()");
		//調用wait,使當前線程進入等待狀態
		try {
			wait();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	public synchronized void show(){
		//正常輸出內容
		System.out.println("tempObj.show()");
	} 
}

//演示sleep不會釋放鎖,而wait是會釋放鎖的
	public static void sleepAndWait() throws InterruptedException{
		final TempObj obj = new TempObj();
		//線程T1
		Thread t1 = new Thread(new Runnable() {
			public void run() {
				System.out.println("run thread t1");
//				obj.bySleep(); //代碼1處
				obj.byWait();
				System.out.println("run thread t1...over...");
			}
		});
		
		//線程T2
		Thread t2 = new Thread(new Runnable() {
			public void run() {
				System.out.println("run thread t2");
				obj.show();
				System.out.println("run thread t2...over");
			}
		});
		
		ExecutorService exec = Executors.newCachedThreadPool();
		exec.execute(t1);
		TimeUnit.MILLISECONDS.sleep(10);
		exec.execute(t2);
		exec.shutdown();
	}
輸出******************************************************************

run thread t1
tempObj.byWait()
run thread t2
tempObj.show()
run thread t2...over

**********************************************************************'

我們開啓了兩個線程T1以及T2,t1先運行,首先打印出【run thread t1】,

它在內部將調用TempObj的byWait方法,接着打印出【tempObj.byWait()】,

接下來在它內部調用了wait(),這將導致當前線程阻塞且釋放鎖,所以沒有打印出【run thread t1...over...】;

而T2線程調用了TempObj的show方法,此時T1已釋放了鎖,所以能打印出【tempObj.show()】

如果我們將obj.byWait()方法註釋,然後將Obj.bySleep()註釋取消,則結果又會是怎樣呢,我們來看看輸出:


輸出******************************************************************

run thread t1
tempObj.bySleep()
run thread t2

**********************************************************************'


由於sleep是不會釋放鎖的,所以T2線程自然無法調用show()方法咯!

我們注意到第一次輸出還少了個內容【run thread t1...over...】 ,這是因爲T1線程一直在等待,而T2線程直接運行完畢並沒有喚醒它,我們稍微改一下代碼:
	public synchronized void show(){
		//正常輸出內容
		System.out.println("tempObj.show()");
		//喚醒正在等待當前對象鎖的所有線程,T1線程正在等待此對象鎖
		notifyAll();
	} 

再次運行,輸出**************************************************************
run thread t1
tempObj.byWait()
run thread t2
tempObj.show()
run thread t2...over
run thread t1...over...
******************************************************************************
沒錯,T2線程與T1線程都運行完畢,這就是我們想要的!

現在我們來思考一個問題,notify只會喚醒等待當前對象鎖的單個線程,但它到底會喚醒哪個線程呢?是隨機的嗎?  notifyAll將喚醒等待當前對象鎖的全部線程,那喚醒的順序又是怎樣的呢?我們寫段代碼來試驗一下!

	//演示notify,notifyAll 的喚醒順序
	public static void notifyAndNotifyAll() throws InterruptedException{
		class Temp{
			public synchronized void show(){
				System.out.println(Thread.currentThread().getName()+".temp.show()");
				try {
					wait();
				} catch (InterruptedException e) {e.printStackTrace();}
				
				System.out.println(Thread.currentThread().getName()+".temp.show()...over...");
			}
		}
		
		final Temp temp = new Temp();
		class MyThread extends Thread{
			public MyThread( String name ) {
				super.setName(name);
			}
			@Override
			public void run() {
				temp.show();
			}
		}
		
		ExecutorService exec = Executors.newCachedThreadPool();
		for (int i = 0; i < 10; i++) {
			exec.execute(new MyThread("線程"+i));
		}
		
		TimeUnit.MILLISECONDS.sleep(1000);
		//注意,wait以及notify等方法調用,必須是在持有其對象鎖的情況下!
		synchronized (temp) {
			temp.notify();
//			temp.notifyAll();
		}
		exec.shutdown();
	}

這代碼比較簡單,我們開啓5個線程去調用temp.show(),這裏將先輸出【線程名字.temp.show()】,然後被阻塞, 被阻塞後,我們分別調用notify()和notifyAll()看看線程被喚醒的順序 ; 我們來看notify()
輸出**************************************************************************
pool-1-thread-1.temp.show()
pool-1-thread-4.temp.show()
pool-1-thread-3.temp.show()
pool-1-thread-5.temp.show()
pool-1-thread-2.temp.show()
pool-1-thread-1.temp.show()...over...
*******************************************************************************

我們在將註釋取消,看看notifyAll()
輸出**************************************************************************
pool-1-thread-2.temp.show()
pool-1-thread-3.temp.show()
pool-1-thread-1.temp.show()
pool-1-thread-5.temp.show()
pool-1-thread-4.temp.show()
pool-1-thread-4.temp.show()...over...
pool-1-thread-5.temp.show()...over...
pool-1-thread-1.temp.show()...over...
pool-1-thread-3.temp.show()...over...
pool-1-thread-2.temp.show()...over...

******************************************************************************


Ok,通過輸出我們得出結論;notify是先喚醒第一個被阻塞的線程; 而notifyAll則相反,它先喚醒最後一個被阻塞的線程!
現在我們知道了這幾個方法的基本用法,下一章我們來寫一 個“積累能量---放大招”  的例子來整合演示這幾個方法的協作運用;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章