一.查看API
sleep是Thread類的方法,導致此線程暫停執行指定時間,給其他線程執行機會,但是依然保持着監控狀態,過了指定時間會自動恢復,調用sleep方法不會釋放鎖對象。
當調用sleep方法后,當前線程進入阻塞狀態。目的是讓出CPU給其他線程運行的機會。但是由於sleep方法不會釋放鎖對象,所以在一個同步代碼塊中調用這個方法后,線程雖然休眠了,但其他線程無法訪問它的鎖對象。這是因為sleep方法擁有CPU的執行權,它可以自動醒來無需喚醒。而當sleep()結束指定休眠時間后,這個線程不一定立即執行,因為此時其他線程可能正在運行。
wait方法是Object類里的方法,當一個線程執行到wait()方法時,它就進入到一個和該對象相關的等待池中,同時釋放了鎖對象,等待期間可以調用里面的同步方法,其他線程可以訪問,等待時不擁有CPU的執行權,否則其他線程無法獲取執行權。當一個線程執行了wait方法后,必須調用notify或者notifyAll方法才能喚醒,而且是隨機喚醒,若是被其他線程搶到了CPU執行權,該線程會繼續進入等待狀態。由於鎖對象可以時任意對象,所以wait方法必須定義在Object類中,因為Obeject類是所有類的基類。
二.是否可以傳入參數
sleep()方法必須傳入參數,參數就是休眠時間,時間到了就會自動醒來。
wait()方法可以傳入參數也可以不傳入參數,傳入參數就是在參數結束的時間后開始等待,不穿如參數就是直接等待。
三.是否需要捕獲異常
sleep方法必須要捕獲異常,而wait方法不需要捕獲異常。sleep方法屬於Thread類中方法,表示讓一個線程進入睡眠狀態,等待一定的時間之后,自動醒來進入到可運行狀態,不會馬上進入運行狀態,因為線程調度機制恢復線程的運行也需要時間,一個線程對象調用了sleep方法之后,並不會釋放他所持有的所有對象鎖,所以也就不會影響其他進程對象的運行。但在sleep的過程中過程中有可能被其他對象調用它的interrupt(),產生InterruptedException異常,如果你的程序不捕獲這個異常,線程就會異常終止,進入TERMINATED狀態,如果你的程序捕獲了這個異常,那么程序就會繼續執行catch語句塊(可能還有finally語句塊)以及以后的代碼。
wait屬於Object的成員方法,一旦一個對象調用了wait方法,必須要采用notify()和notifyAll()方法喚醒該進程;如果線程擁有某個或某些對象的同步鎖,那么在調用了wait()后,這個線程就會釋放它持有的所有同步資源,而不限於這個被調用了wait()方法的對象。wait()方法也同樣會在wait的過程中有可能被其他對象調用interrupt()方法而產生。
四.作用范圍
wait、notify和notifyAll方法只能在同步方法或者同步代碼塊中使用,而sleep方法可以在任何地方使用。但是注意sleep是靜態方法,也就是說它只對當前對象有效。通過對象名.sleep()想讓該對象線程進入休眠是無效的,它只會讓當前線程進入休眠。
五.調用者的區別
首先為什么wait、notify和notifyAll方法要和synchronized關鍵字一起使用?
因為wait方法是使一個線程進入等待狀態,並且釋放其所持有的鎖對象,notify方法是通知等待該鎖對象的線程重新獲得鎖對象,然而如果沒有獲得鎖對象,wait方法和notify方法都是沒有意義的,因此必須先獲得鎖對象再對鎖對象進行進一步操作於是才要把wait方法和notify方法寫到同步方法和同步代碼塊中了。
由此可知,wait和notify、notifyAll方法是由確定的對象即鎖對象來調用的,鎖對象就像一個傳話的人,他對某個線程說停下來等待,然后對另一個線程說你可以執行了(實質上是被捕獲了),這一過程是線程通信。sleep方法是讓某個線程暫停運行一段時間,其控制范圍是由當前線程決定,運行的主動權是由當前線程來控制(擁有CPU的執行權)。
其實兩者的區別都是讓線程暫停運行一段時間,但本質的區別一個是線程的運行狀態控制,一個是線程間的通信。
六.wait方法(線程通信)的一些注意事項
例如以下代碼:
當線程print1獲得了鎖對象(this)后進行判斷,如果不滿足條件(flag == 1)無法繼續執行下面的代碼,於是進入wait狀態。在另一個線程print3中,由於改變了flag,使得線程print1的判斷條件滿足了,線程print1就會被喚醒。
class Printer { private int flag = 1; public void print1() throws Exception { synchronized(this) { while(flag != 1) { this.wait(); } System.out.print("1"); System.out.print("2"); System.out.print("3"); System.out.print("4"); System.out.println(); flag = 2; this.notifyAll(); } } public void print2() throws Exception { synchronized(this) { while(flag != 2) { this.wait(); } System.out.print("5"); System.out.print("6"); System.out.print("7"); System.out.print("8"); System.out.println(); flag = 3; this.notifyAll(); } } public void print3() throws Exception { synchronized(this) { while(flag != 3) { this.wait(); } System.out.print("A"); System.out.print("B"); System.out.print("C"); System.out.print("D"); System.out.println(); flag = 1; this.notifyAll(); } } }
注意事項:
1.調用wait方法和notify、notifyAll方法前必須獲得對象鎖,也就是必須寫在synchronized(鎖對象){......}代碼塊中。
2.當線程print1調用了wait方法后就釋放了對象鎖,否則其他線程無法獲得對象鎖,也就無法喚醒線程print1。
3.當this.wait()方法返回后,線程必須再次獲得對象鎖后才能繼續執行。
4.如果另外兩個線程都在wait,則正在執行的線程調用notify方法只能喚醒一個正在wait的線程(公平競爭,由JVM決定)。
5.當使用notifyAll方法后,所有wait狀態的線程都會被喚醒,但是只有一個線程能獲得鎖對象,必須執行完while(condition){this.wait();}后才能獲得對象鎖。其余的需要等待該獲得對象鎖的線程執行完釋放對象鎖后才能繼續執行。
6.當某個線程調用notifyAll方法后,雖然其他線程被喚醒了,但是該線程依然持有着對象鎖,必須等該同步代碼塊執行完(右大括號結束)后才算正式釋放了鎖對象,另外兩個線程才有機會執行。