一、線程的狀態
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過時的原因?