sleep、yield、wait、join的區別(阿里)


只有runnable到running時才會占用cpu時間片,其他都會出讓cpu時間片。
線程的資源有不少,但應該包含CPU資源和鎖資源這兩類。
sleep(long mills):讓出CPU資源,但是不會釋放鎖資源。
wait():讓出CPU資源和鎖資源。

1.  Thread.sleep(long) 和Thread.yield()都是Thread類的靜態方法,在調用的時候都是Thread.sleep(long)/  Thread.yield()的方式進行調用。

   而join()是由線程對象來調用。

2.wait()和notify()、notifyAll()  這三個方法都是java.lang.Object的方法! 

Object 是java.lang.Object,因為天天說Java是面向對象的,所以Object是所有Java對象的超類,都實現Object的方法:

Object參考:Java超類-java.lang.object

它們都是用於協調多個線程對共享數據的存取,所以必須在Synchronized語句塊內使用這三個方法。前面說過Synchronized這個關鍵字用於保護共享數據,阻止其他線程對共享數據的存取。但是這樣程序的流程就很不靈活了,如何才能在當前線程還沒退出Synchronized數據塊時讓其他線程也有機會訪問共享數據呢?此時就用這三個方法來靈活控制。 

(1) wait()方法使當前線程暫停執行並釋放對象鎖標志,讓其他線程可以進入Synchronized數據塊,當前線程被放入對象等待池中。

(2) 當調用 notify()方法后,將從對象的等待池中移走一個任意的線程並放到鎖標志等待池中,只有鎖標志等待池中的線程能夠獲取鎖標志;如果鎖標志等待池中沒有線程,則notify()不起作用。 
(3) notifyAll()則從對象等待池中移走所有等待那個對象的線程並放到鎖標志等待池中。 

 

sleep與Wait的區別:sleep是線程方法,wait是object方法;看區別,主要是看CPU的運行機制:

它們的區別主要考慮兩點:1.cpu是否繼續執行、2.鎖是否釋放掉。

對於這兩點,首先解釋下cpu是否繼續執行的含義:cpu為每個線程划分時間片去執行,每個時間片時間都很短,cpu不停地切換不同的線程,以看似他們好像同時執行的效果。

其次解釋下鎖是否釋放的含義:鎖如果被占用,那么這個執行代碼片段是同步執行的,如果鎖釋放掉,就允許其它的線程繼續執行此代碼塊了。 

明白了以上兩點的含義,開始分析sleep和wait:

sleep ,釋放cpu資源,不釋放鎖資源,如果線程進入sleep的話,釋放cpu資源,如果外層包有Synchronize,那么此鎖並沒有釋放掉。

wait,釋放cpu資源,也釋放鎖資源,一般用於鎖機制中 肯定是要釋放掉鎖的,因為notify並不會立即調起此線程,因此cpu是不會為其分配時間片的,也就是說wait 線程進入等待池,cpu不分時間片給它,鎖釋放掉。

(wait用於鎖機制,sleep不是,這就是為啥sleep不釋放鎖,wait釋放鎖的原因,sleep是線程的方法,跟鎖沒半毛錢關系,wait,notify,notifyall 都是Object對象的方法,是一起使用的,用於鎖機制)

 

最后: 

1.sleep:Thread類的方法,必須帶一個時間參數。會讓當前線程休眠進入阻塞狀態並釋放CPU(阿里面試題 Sleep釋放CPU,wait 也會釋放cpu,因為cpu資源太寶貴了,只有在線程running的時候,才會獲取cpu片段),提供其他線程運行的機會且不考慮優先級,但如果有同步鎖則sleep不會釋放鎖即其他線程無法獲得同步鎖  可通過調用interrupt()方法來喚醒休眠線程。

 

2.yield:讓出CPU調度,Thread類的方法,類似sleep只是不能由用戶指定暫停多長時間 ,並且yield()方法只能讓同優先級的線程有執行的機會。 yield()只是使當前線程重新回到可執行狀態,所以執行yield()的線程有可能在進入可執行狀態后馬上又被執行。調用yield方法只是一個建議,告訴線程調度器我的工作已經做的差不多了,可以讓別的相同優先級的線程使用CPU了,沒有任何機制保證采納。

 

3.wait:Object類的方法(notify()、notifyAll()  也是Object對象),必須放在循環體和同步代碼塊中,執行該方法的線程會釋放鎖,進入線程等待池中等待被再次喚醒(notify隨機喚醒,notifyAll全部喚醒,線程結束自動喚醒)即放入鎖池中競爭同步鎖

 

4.join:一種特殊的wait,當前運行線程調用另一個線程的join方法,當前線程進入阻塞狀態直到另一個線程運行結束等待該線程終止。 注意該方法也需要捕捉異常。

等待調用join方法的線程結束,再繼續執行。如:t.join();//主要用於等待t線程運行結束,若無此句,main則會執行完畢,導致結果不可預測。

 

關於Java中線程的生命周期,首先看一下下面這張較為經典的圖:

 

 

上圖中基本上囊括了Java中多線程各重要知識點。掌握了上圖中的各知識點,Java中的多線程也就基本上掌握了。主要包括:

線程的5個狀態 

1、新建狀態(New):當線程對象對創建后,即進入了新建狀態,如:Thread t = new MyThread();

2、就緒狀態(Runnable):當調用線程對象的start()方法(t.start();),線程即進入就緒狀態。處於就緒狀態的線程,只是說明此線程已經做好了准備,隨時等待CPU調度執行,獲取cpu 的使用權,並不是說執行了t.start()此線程立即就會執行

3、運行狀態(Running):可運行狀態(runnable)的線程獲得了cpu 時間片(timeslice) ,執行程序代碼。 當CPU開始調度處於就緒狀態的線程時,此時線程才得以真正執行,即進入到運行狀態。注:就 緒狀態是進入到運行狀態的唯一入口,也就是說,線程要想進入運行狀態執行,首先必須處於就緒狀態中;

4、阻塞狀態(Blocked):處於運行狀態中的線程由於某種原因,暫時放棄對CPU的使用權,,也即讓出了cpu timeslice, 停止執行,此時進入阻塞狀態,直到其進入到就緒狀態,才 有機會再次被CPU調用以進入到運行狀態。才有機會再次獲得cpu timeslice 轉到運行(running)狀態 根據阻塞產生的原因不同,阻塞狀態又可以分為三種:

 

1.等待阻塞:運行狀態中的線程執行wait()方法,使本線程進入到等待阻塞狀態;,JVM會把該線程放入等待隊列(waitting queue)中。

2.同步阻塞 –,運行(running)的線程在獲取對象的同步鎖時,若該同步鎖被別的線程占用,獲取synchronized同步鎖失敗 , 它會進入同步阻塞狀態 ,則JVM會把該線程放入鎖池(lock pool)中。

3.其他阻塞 – 通過調用線程的sleep()或join()或發出了I/O請求時,線程會進入到阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。運行(running)的線程執行Thread.sleep(long ms)或t.join()方法,或者發出了I/O請求時,JVM會把該線程置為阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入可運行(runnable)狀態。

5、死亡狀態(Dead):線程執行完了或者因異常退出了run()方法,該線程結束生命周期。

從圖中可以看出,只有runnable到running時才會占用cpu時間片,其他都會出讓cpu時間片。
線程的資源有不少,但應該包含CPU資源和鎖資源這兩類。
sleep(long mills):讓出CPU資源,但是不會釋放鎖資源。
wait():讓出CPU資源和鎖資源。
鎖是用來線程同步的,sleep(long mills)雖然讓出了CPU,但是不會讓出鎖,其他線程可以利用CPU時間片了,但如果其他線程要獲取sleep(long mills)擁有的鎖才能執行,則會因為無法獲取鎖而不能執行,繼續等待。
但是那些沒有和sleep(long mills)競爭鎖的線程,一旦得到CPU時間片即可運行了。 

 

5. 死亡(DEAD):線程run()、main() 方法執行結束,或者因異常退出了run()方法,則該線程結束生命周期。死亡的線程不可再次復生。

我寫了個例子:

public class abc_test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub 
        
        Thread thread =new Thread(new joinDemo());
        thread.start();
        
        for(int i=0;i<20;i++){
            
            System.out.println("主線程第"+i+"此執行!");
            
            if(i>=2){
                
                try{
                    //t1線程合並到主線程中,主線程停止執行過程,轉而執行t1線程,直到t1執行完畢后繼續;
                    thread.join();
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
        
        
        

    }

}

class joinDemo implements Runnable{

    @Override
    public void run() {
        // TODO Auto-generated method stub
        
        for(int i=0;i<10;i++){
            
            System.out.println("線程1第"+i+"次執行");
        }
        
    }
    
      
     
}

結果為:

主線程第0此執行!
主線程第1此執行!
主線程第2此執行!
線程1第0次執行
線程1第1次執行
線程1第2次執行
線程1第3次執行
線程1第4次執行
線程1第5次執行
線程1第6次執行
線程1第7次執行
線程1第8次執行
線程1第9次執行
主線程第3此執行!
主線程第4此執行!
主線程第5此執行!
主線程第6此執行!
   .....
主線程第19此執行!

也可參考我專門寫的關於線程狀態的文章:Java線程的5種狀態及切換(透徹講解)-京東面試

參考:主題:sleep,wait,join,yield有何差別?

參考:java之yield(),sleep(),wait()區別詳解-備忘筆記

參考:sleep、yield、wait、join的區別

參考:sleep,yield,join,notify,wait,notifyAll區別

參考:線程sleep和對象wait一段時間的區別

參考:Java線程中sleep()、wait()和notify()和notifyAll()、yield()、join()等方法的用法和區別

參考:理解線程狀態,答疑wait與sleep是否占用cpu資源的問題,再來個小demo

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM