Java 多線程基礎(六)線程等待與喚醒
遇到這樣一個場景,當某線程里面的邏輯需要等待異步處理結果返回后才能繼續執行。或者說想要把一個異步的操作封裝成一個同步的過程。這里就用到了線程等待喚醒機制。
一、wait()、notify()、notifyAll() 等方法介紹
在 Object 中,定義了 wait()、notify() 和 notifyAll() 等接口。wait() 的作用是讓當前線程進入等待狀態,同時,wait() 也會讓當前線程釋放它所持有的鎖。而 notify() 和 notifyAll() 的作用,則是喚醒當前對象上的等待線程;notify() 是喚醒單個線程,而 notifyAll() 是喚醒所有的線程。
Object類中關於等待/喚醒的API詳細信息如下:
notify() -- 喚醒在此對象監視器上等待的單個線程。
notifyAll() -- 喚醒在此對象監視器上等待的所有線程。
wait() -- 讓當前線程處於“等待(阻塞)狀態”,“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法”,當前線程被喚醒(進入“就緒狀態”)。
wait(long timeout) -- 讓當前線程處於“等待(阻塞)狀態”,“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者超過指定的時間量”,當前線程被喚醒(進入“就緒狀態”)。
wait(long timeout, int nanos) -- 讓當前線程處於“等待(阻塞)狀態”,“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者其他某個線程中斷當前線程,或者已超過某個實際時間量”,當前線程被喚醒(進入“就緒狀態”)。
二、wait() 和 notify() 示例
public class Demo02 { public static void main(String[] args) { Thread t1 = new MyThread("t1"); synchronized (t1) { try { // 啟動“線程t1” System.out.println(Thread.currentThread().getName()+" start t1"); t1.start(); // 主線程等待t1通過notify()喚醒。 System.out.println(Thread.currentThread().getName()+" wait()"); t1.wait(); System.out.println(Thread.currentThread().getName()+" continue"); } catch (InterruptedException e) { e.printStackTrace(); } } } } class MyThread extends Thread{ public MyThread(String name) { super(name); } @Override public void run() { synchronized (this) { try { System.out.println(Thread.currentThread().getName()+" call notify()"); notify(); // 喚醒當前的Demo02線程 }catch(Exception e) { e.printStackTrace(); } } } }
// 運行結果 main start t1 main wait() t1 call notify() main continue
說明:
①、 注意,圖中"主線程" 代表“主線程main”。"線程t1" 代表Demo02中啟動的“線程t1”。 而“鎖” 代表“t1這個對象的同步鎖”。
②、“主線程”通過 new ThreadA("t1") 新建“線程t1”。隨后通過synchronized(t1)獲取“t1對象的同步鎖”。然后調用t1.start()啟動“線程t1”。
③、“主線程”執行t1.wait() 釋放“t1對象的鎖”並且進入“等待(阻塞)狀態”。等待t1對象上的線程通過notify() 或 notifyAll()將其喚醒。
④、“線程t1”運行之后,通過synchronized(this)獲取“當前對象的鎖”;接着調用notify()喚醒“當前對象上的等待線程”,也就是喚醒“主線程”。
⑤、“線程t1”運行完畢之后,釋放“當前對象的鎖”。緊接着,“主線程”獲取“t1對象的鎖”,然后接着運行。
具體過程圖解
三、wait(long timeout) 和 notify()
public class Demo02 { public static void main(String[] args) { Thread t1 = new MyThread("t1"); synchronized(t1) { try { // 啟動線程t1 System.out.println(Thread.currentThread().getName() + " start t1"); t1.start(); // 主線程等待t1通過notify()喚醒 或 notifyAll()喚醒,或超過3s延時;然后才被喚醒。 System.out.println(Thread.currentThread().getName() + " call wait "); t1.wait(3000); System.out.println(Thread.currentThread().getName() + " continue"); } catch (InterruptedException e) { e.printStackTrace(); } } } } class MyThread extends Thread{ public MyThread(String name) { super(name); } public void run() { System.out.println(Thread.currentThread().getName() + " run "); // 死循環,不斷運行。 while(true) ; } }
// 運行結果 main start t1 main call wait t1 run // 3秒后輸出 main continue main continue
說明:
如下圖,說明了“主線程”和“線程t1”的流程。
①、注意,圖中"主線程" 代表線程main。"線程t1" 代表MyThread中啟動的線程t1。 而“鎖” 代表“t1這個對象的同步鎖”。
②、主線程main執行t1.start()啟動“線程t1”。
③、主線程main執行t1.wait(3000),此時,主線程進入“阻塞狀態”。需要“用於t1對象鎖的線程通過notify() 或者 notifyAll()將其喚醒” 或者 “超時3000ms之后”,主線程main才進入到“就緒狀態”,然后才可以運行。
④、“線程t1”運行之后,進入了死循環,一直不斷的運行。
⑤、超時3000ms之后,主線程main會進入到“就緒狀態”,然后接着進入“運行狀態”。
具體過程圖解:
四、wait() 和 notifyAll()
public class Demo02 { private static Object obj = new Object(); public static void main(String[] args) { MyThread t1 = new MyThread("t1"); MyThread t2 = new MyThread("t2"); MyThread t3 = new MyThread("t3"); t1.start(); t2.start(); t3.start(); try { System.out.println(Thread.currentThread().getName()+" sleep(5000)"); Thread.sleep(5000); // 休眠5秒 } catch (InterruptedException e) { e.printStackTrace(); } synchronized (obj) { System.out.println(Thread.currentThread().getName()+" notifyAll()"); obj.notifyAll(); } } static class MyThread extends Thread{ public MyThread(String name) { super(name); } public void run() { synchronized (obj) { try { System.out.println(Thread.currentThread().getName() + " run "); obj.wait(); System.out.println(Thread.currentThread().getName() + " continue"); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
// 運行結果 t1 run t2 run main sleep(5000) t3 run main notifyAll() t3 continue t2 continue t1 continue
說明:
①、 主線程中新建並且啟動了3個線程"t1", "t2"和"t3"。
②、主線程通過sleep(5000)休眠5秒。在主線程休眠3秒的過程中,我們假設"t1", "t2"和"t3"這3個線程都運行了。以"t1"為例,當它運行的時候,它會執行obj.wait()等待其它線程通過notify()或nofityAll()來喚醒它;相同的道理,"t2"和"t3"也會等待其它線程通過nofity()或nofityAll()來喚醒它們。
③、主線程休眠3秒之后,接着運行。執行 obj.notifyAll() 喚醒obj上的等待線程,即喚醒"t1", "t2"和"t3"這3個線程。 緊接着,主線程的synchronized(obj)運行完畢之后,主線程釋放“obj鎖”。這樣,"t1", "t2"和"t3"就可以獲取“obj鎖”而繼續運行了!
具體過程圖解
五、 為什么notify(), wait()等函數定義在Object中,而不是Thread中
Object中的wait(), notify()等函數,和synchronized一樣,會對“對象的同步鎖”進行操作。
wait()會使“當前線程”等待,因為線程進入等待狀態,所以線程應該釋放它鎖持有的“同步鎖”,否則其它線程獲取不到該“同步鎖”而無法運行!
OK,線程調用wait()之后,會釋放它鎖持有的“同步鎖”;而且,根據前面的介紹,我們知道:等待線程可以被notify()或notifyAll()喚醒。現在,請思考一個問題:notify()是依據什么喚醒等待線程的?或者說,wait()等待線程和notify()之間是通過什么關聯起來的?答案是:依據“對象的同步鎖”。
負責喚醒等待線程的那個線程(我們稱為“喚醒線程”),它只有在獲取“該對象的同步鎖”(這里的同步鎖必須和等待線程的同步鎖是同一個),並且調用notify()或notifyAll()方法之后,才能喚醒等待線程。雖然,等待線程被喚醒;但是,它不能立刻執行,因為喚醒線程還持有“該對象的同步鎖”。必須等到喚醒線程釋放了“對象的同步鎖”之后,等待線程才能獲取到“對象的同步鎖”進而繼續運行。
總之,notify(), wait()依賴於“同步鎖”,而“同步鎖”是對象鎖持有,並且每個對象有且僅有一個!這就是為什么notify(), wait()等函數定義在Object類,而不是Thread類中的原因。