Java線程的狀態和狀態轉換


一、線程的狀態

1.Java中的6種線程狀態

Java語言定義了6種線程狀態

    public enum State {
        NEW,
        RUNNABLE,
        BLOCKED,
        WAITING,
        TIMED_WAITING,
        TERMINATED;
    }

新建(New)

線程創建后尚未啟動。

可運行(Runnable)

一旦調用了start方法,線程就處於可運行狀態。可運行狀態的線程可能正在運行,也可能還沒有運行而正在等待 CPU 時間片。(Java規范中並沒有分為可運行狀態和正在運行狀態這兩個狀態,而是把它們合為一個狀態。所以,可以把一個正在運行中的線程仍然稱其處於可運行狀態。)

阻塞(Blocked)

處於阻塞狀態的線程並不會占用CPU資源。

以下情況會讓線程進入阻塞狀態:

①等待獲取鎖

等待獲取一個鎖,而該鎖被其它線程持有,則該線程進入阻塞狀態。當其它線程釋放了該鎖,並且線程調度器允許該線程持有該鎖時,該線程退出阻塞狀態。

②IO阻塞

線程發起了一個阻塞式IO后也會進入阻塞狀態。最典型的場景,如等待用戶輸入內容然后繼續執行。

無限期等待(Waiting)

處於這種狀態的線程不會被分配 CPU 時間片,需要等待其它線程顯式地喚醒。

以下方法會讓線程進入無限期的等待狀態

  • Object.wait() 方法                    結束:Object.notify() / Object.notifyAll()
  • Thread.join() 方法                    結束:被調用的線程執行完畢
  • LockSupport.park() 方法          結束:LockSupport.unpark(currentThread)

限時等待(Timed Waiting)

處於這種狀態的線程也不會被分配CPU 時間片,在一定時間之后會被系統自動喚醒。

以下方法會讓線程進入限期等待狀態:

  • Thread.sleep(time) 方法          結束:sleep時間結束
  • Object.wait(time) 方法             結束:wait時間結束,或者Object.notify() / notifyAll()    
  • LockSupport.parkNanos(time)/parkUntil(time) 方法     結束:park時間結束,或者LockSupport.unpark(當前線程)

死亡(Terminated)

可以是線程結束任務之后自己結束,或者產生了異常而結束。

 

注意:在有些書籍(如《Java並發編程實戰》中5.4章節)或籠統的稱呼中,就將阻塞、等待和超時等待這三種狀態統稱為阻塞狀態。

2.幾種狀態的轉換

二、等待、阻塞、中斷、睡眠掛起

  描述 釋放鎖 cpu
等待 線程因等待某個條件而進入等待狀態 會釋放鎖 不會被分配CPU時間片
阻塞 線程因競爭鎖失敗而進入阻塞狀態 未獲取到鎖  
睡眠 讓出CPU的使用權讓其它線程執行 不會釋放鎖 讓出CPU的使用權
掛起      
yield 讓出CPU的使用權,進入就緒狀態(runnable)   讓出CPU的使用權
中斷

並非終止線程,而是給該線程發送一個中斷通知,讓其自行決定在合適的時間對中斷通知做出響應(響應可以是終止線程,或者不做出響應)。

   
join

等待調用該方法的線程執行完畢后,線程再往下繼續執行

   

 

1.阻塞狀態和等待狀態的區別

阻塞狀態:等待獲取鎖或者IO阻塞。

等待狀態:因條件的不允許而暫停運行,會釋放鎖。

更多的細節可以參考:線程同步機制 — 鎖的內置鎖的調度(監視器模型)這部分內容。

2.wait和sleep的區別

①sleep來自Thread類,和wait來自Object類。

②sleep不會釋放鎖,而wait會釋放了鎖。

③sleep必須捕獲異常,而wait/notify/notifyAll不需要捕獲異常。

④wait/notify/notifyAll必須在同步方法或同步代碼塊中調用,而sleep沒有這方面的限制。 (原因見本文末)

3.interrupt

線程中斷並非線程終止,而是要給該線程發一個中斷信號讓它自己決定如何處理。

實際上是設置了線程的中斷標志位,在線程阻塞的地方(如調用sleep、wait、join等地方)拋出一個異常InterruptedException,並且中斷狀態也將被清除,重新復位為false,這樣線程就得以退出阻塞的狀態。

關於interrupt的介紹可以參考博客:任務的取消 

三、線程常見的方法

1.sleep

使當前線程休眠(暫停運行)指定的時間。

用法:Thread.sleep(time)

Thread.sleep(0)

Thread.sleep(0)的作用是觸發操作系統立刻重新進行一次CPU競爭。

參考文章:代碼中的Thread.sleep(0) 有什么意義?是寫錯了嗎?

2.join

等待相應線程運行結束。若線程A調用線程B的join方法,那么線程A將會暫停運行,直到線程B運行結束。(誰join進來就等誰先運行完)

join實際上是通過wait來實現的。

3.yield

使當前線程主動放棄對處理器的占用,這可能導致當前線程被暫停。

這個方法是不可靠的,該方法被調用時當前線程可能仍然繼續運行(視系統當前的運行狀況而定)。

4.wait和notify()/notifyAll()

需要注意的是

①wait和notify/notifyAll方法只能在同步代碼塊里調用

wait()、notify()、notifyAll() 這三個方法能夠被調用的前提是已經獲取了相應的互斥鎖,所以我們會發現 wait()、notify()、notifyAll() 都是在synchronized{}內部被調用的

②notify操作必須要在wait操作之后,否則,可能導致線程不會醒來。

③wait

5.LockSupport.park()/parkNano(time)/parkUntil(time) 

LockSupport比Object的wait/notify有兩大優勢:

①LockSupport不需要在同步代碼塊里 。所以線程間也不需要維護一個共享的同步對象了,實現了線程間的解耦。

②unpark方法可以先於park方法調用,所以不需要擔心線程間的執行的先后順序。

 

四、線程已過時的方法

stop

官方的解釋:Why is Thread.stop deprecated?

stop被廢棄的原因是stop太過於暴力,強行把線程終止,可能會引起數據的不一致性。

suspend/resume

suspend和resume是一對相反的操作,被suspend的線程必須要等到resume操作才能繼續執行。

suspend在掛起線程時並不會釋放任務鎖資源,其它線程想要訪問被它持有的鎖都會收到牽連而導致無法繼續運行。

而如果resume操作意外地在suspend之前先執行,那么被掛起的線程很難有機會被繼續執行了。而且被掛起的線程從線程狀態上看居然還是Runnable,這也會嚴重影響我們對系統當前狀態的判斷。

五、關於wait的使用注意事項

1.wait/notify/notifyAll必須在同步方法或同步代碼塊中調用

wait/notify/notifyAll必須在同步方法或同步代碼塊中調用,否則會拋出IllegalMonitorStateException異常wait會釋放鎖,同時讓線程等待。所以必須在同步方法或同步代碼塊中調用,表示已經獲取了鎖,然后才能釋放鎖。

原問題出處:Why wait(), notify(), notifyAll() must be called inside a synchronized method/block?

權威解釋:Chapter 20 of Inside the Java Virtual Machine - Thread Synchronization by Bill Venners (《深入Java虛擬機》第二版第20章)

中文翻譯:為什么wait(),notify(),notifyAll()必須在同步方法/代碼塊中調用?

2.wait為什么要在while循環,而非if中調用?

使用wait時需要將其放在while循環中,而非使用if。我們通過下面的例子來分析!

produce和consume方法用來模擬生產者和消費者方法。

produce方法中,當隊列未滿時才進行生產放入隊列,否則讓線程等待。這里就需要注意了,當處於等待狀態的線程被喚醒后是否就能立即生產呢?答案顯然是否定的,因為多個線程在同時操作隊列,當線程被喚醒后很可能有其它生產者線程又將隊列填滿了,所以只能讓線程繼續等待,而使用if顯然是不能達到此目的,必須使用while,表示被喚醒后必須重新檢查條件是否滿足,只有滿足條件后才能繼續往下執行,否則只能繼續等待。

同理consume方法中也是一樣,也必須將wait放在while中,表示消費者線程被喚醒后必須先檢查條件是否滿足。

    public void produce() {
        synchronized (this) {
            while (queue.isFull()) {//使用while,而非if
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
            queue.add("object");//生產
            notifyAll();
        }
    }

    public void consume() {
        synchronized (this) {
            while (queue.isEmpty()) {//使用while,而非if
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
            queue.remove();//消費
            notifyAll();
        }
    }

 

常見問題匯總:

1.java中線程的幾種狀態?狀態間的轉換?

2.掛起和等待有什么區別?

3.wait和sleep的區別?

4.sleep(0)有什么含義?

5.wait、notify/notifyAll為什么要在synchronized塊中調用?

6.wait為什么要在while循環而非if中調用?

7.interrupt()和static interrupted()兩者的區別?

public void interrupt():中斷線程

public boolean interrupted():判斷是否被中斷

public static boolean interrupted():判斷是否被中斷,並清除當前中斷狀態。

8.stop,suspend/resume過時的原因?

 


免責聲明!

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



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