執行緒生命週期5種狀態
介紹
執行緒的生命週期經過新建(New)、就緒(Runnable)、執行(Running)、阻塞(Bolocked)和死亡(Dead)
狀態轉換圖
新建(New)
程式使用new關鍵字
建立一個執行緒之後,該執行緒就處於新建狀態
,僅僅由Java虛擬機器為其分配記憶體,並初始化其成員變數的值。不會執行執行緒的執行緒執行體。如Thread thread = new Thread()
。
就緒(Runnable)
也稱為“可執行狀態”
,執行緒物件呼叫start()
方法後,該執行緒處於就緒狀態
。如thread.start()
。Java虛擬機器會為其建立方法呼叫棧和程式計數器(執行緒私有),處於就緒狀態的執行緒並沒有開始執行,只是表示該執行緒可以執行,執行緒何時執行取決於JVM中執行緒排程器的排程。
執行(Running)
處於就緒狀態的執行緒獲得CPU,開始執行run()方法
的執行緒執行體,則該執行緒處於執行狀態
。(注意:執行緒只能從就緒狀態進入到執行狀態)
阻塞(Boloked)
阻塞狀態是執行緒因為某種原因放棄了CPU的使用權
,暫時停止執行,直到執行緒進入就緒狀態,才有機會轉到執行狀態。當呼叫sleep()
、一個阻塞式IO方法
、同步鎖、等待通知、suspend()方法
掛起都會使執行緒進入阻塞狀態。
- 執行緒呼叫
sleep()
方法主動放棄所佔用的處理器資源; - 執行緒呼叫一個阻塞式
IO方法
,在該方法返回之前,該執行緒被阻塞; - 執行緒試圖獲得一個
同步監視器
,但該同步監視器正被其他執行緒所持有; - 執行緒在等待(
wait()
)某個通知(notify()
); - 程式呼叫了執行緒的
suspend()
方法將該執行緒掛起,但這個方法易造成死鎖,應該避免使用。
執行緒從阻塞狀態解除——進入就緒狀態的過程:
- 呼叫
sleep()方法
的執行緒經過了指定時間; - 執行緒呼叫的阻塞式IO方法已經返回;
- 執行緒成功地獲得試圖取得的同步監視器(鎖);
- 執行緒正在等待某個通知時,其他執行緒發出了一個通知;
- 處於掛起狀態的執行緒被呼叫了
resume()
恢復方法。
死亡(Dead)
以如下3種方式結束執行緒
run()
或call()
方法執行完成,執行緒正常結束;- 執行緒丟擲一個未捕獲的
Exception
或Error
; - 直接呼叫該執行緒的
stop()
方法來結束該執行緒(該方法易造成死鎖,不推薦使用)
注意:
- 當丟擲一個異常後程式會結束,所以執行緒會終止;
- sleep()方法會阻塞一個執行緒並不會終止;
- 建立一個新的執行緒也不會終止另一個執行緒。
判斷執行緒是否死亡
可以通過isAlive()
方法,執行緒物件的isAlive()
方法返回true,即為執行緒存活;返回false,即為執行緒死亡。
執行緒處於就緒、執行、阻塞狀態時,isAlive()
返回true
;執行緒處於新建、死亡狀態時,isAlive()
返回false
。
start()和run()方法詳解
start()和run()介紹
當程式使用new關鍵字
建立了一個執行緒後,該執行緒就處於新建狀態,此時它和其他Java物件是一樣的,只是由JVM為其分配記憶體,並初始化其成員變數的值(此時執行緒物件沒有任何的行為,也不執行執行緒執行體)。
當執行緒物件呼叫了start()
方法後,執行緒就處於就緒狀態,JVM為其建立方法呼叫棧和程式計數器,處於這個狀態中的執行緒還沒有真正的開始執行,只是表示這個執行緒此時是一個可執行狀態。何時能執行?取決於JVM的執行緒排程器的排程。
處於就緒狀態的執行緒獲取CPU執行許可權
,開始執行run()
方法的執行緒執行體,此時執行緒處於執行狀態。(若只有一個CPU,任何時刻只能有一個執行緒處於執行狀態,多執行緒處理任務時,會給人一種併發錯覺,實際是CPU執行速度較快,多執行緒交織執行任務而已)
start()方法原始碼
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
//若執行緒不是就緒狀態,就丟擲異常
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
//將執行緒新增到ThreadGroup中
group.add(this);
boolean started = false;
try {
//通過start0()方法啟動執行緒
start0();
//設定執行緒啟動的started標誌位
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
start()
實際上通過本地方法start0()
啟動執行緒,會新執行一個執行緒,新執行緒會呼叫run()
方法。
run()方法原始碼
@Override
public void run() {
if (target != null) {
target.run();
}
}
target
是Runnable物件
,run()
直接呼叫Thread執行緒
的Runnable成員
的run()
方法,並不會新建一個執行緒。
執行緒控制方法
sleep()方法
sleep()方法介紹
- sleep(long millis)方法是Thread類的一個靜態方法,作用是讓當前執行緒暫停一段時間,並進入阻塞狀態。
sleep()方法過載方式
public static native void sleep(long millis) throws InterruptedException
:讓當前正在執行的執行緒暫停millis毫秒,並進入阻塞狀態。public static void sleep(long millis, int nanos) throws InterruptedException
:讓當前正在執行的執行緒暫停millis毫秒+nanos毫微秒,並進入阻塞狀態。(很少用)
sleep()示例
通常用法就是
//讓當前執行緒睡眠1000毫秒,即暫定1s
Thread.sleep(1000);
yield()方法
yield()方法介紹
yield()
方法讓當前正在執行的執行緒暫停,但不會阻塞執行緒,只是讓執行緒轉入就緒狀態。yield()
方法讓當前執行緒暫停,讓系統的執行緒排程重新排程一次,所以會出現當某個執行緒呼叫了yield()方法後,執行緒排程器又重新將它排程出來執行。yield()
方法讓當前執行緒暫停後,只有優先順序>=當前執行緒的處於就緒狀態的執行緒才能獲取CPU執行許可權。
yield()方法過載
public static native void yield();
:靜態方法。
yield()示例
//讓當前執行緒暫停
Thread.yield();
執行緒優先順序
- 每個執行緒執行都有一定的優先順序,優先順序高的執行緒獲得CPU執行許可權的機會比較大。
- 每個執行緒預設的優先順序與建立它的父執行緒的優先順序相同。所以main執行緒的優先順序一般和自己建立的子執行緒優先順序一樣。
- Thread類提供
setPriority(int newPriority)
和getPriority()
方法設定和返回指定執行緒的優先順序。其中setPriority()方法的引數可以是一個整數(1-10之間),也可以是靜態常量。
MAX_PRIORITY:值為10.
MIN_PRIORITY:值為1.
NORM_PRIORITY:值為5.
join()方法
join()方法介紹
- Thread類提供
join()方法
讓一個執行緒等待另一個執行緒完成的方法;就是將指定的執行緒加入到當前執行緒,這樣兩個交替執行的執行緒就變成了順序執行的執行緒,如執行緒A呼叫了執行緒B的join()方法,則執行緒A會等待執行緒B執行完畢後才會繼續執行自己。 join()
方法由使用執行緒的程式呼叫,呼叫執行緒呼叫執行緒t的t.join()方法後將會被阻塞,直到執行緒t執行完畢,呼叫執行緒才能繼續執行。一般就是用於主執行緒內,等待其他執行緒執行完畢後,再繼續執行main()函式。
join()方法過載方式
public final void join() throws InterruptedException
:等待被join的執行緒執行完畢。public final synchronized void join(long millis) throws InterruptedException
:等待被join的執行緒的超時時間為millis毫秒。如果在millis毫秒內被join的執行緒還未結束執行流程,則呼叫執行緒不再等待。public final synchronized void join(long millis, int nanos) throws InterruptedException
:等待被join的執行緒的時間最長為millis毫秒+nanos毫微秒。(很少用)
join()方法示例
(1)未使用join()方法
程式碼
public class JoinMethodTest {
public static void main(String[] args) {
System.out.println("main thread start");
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("child thread start");
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("child thread finshed");
}
});
thread.start();
System.out.println("main thread finshed");
}
}
執行結果
main thread start
main thread finshed
child thread start
child thread finshed
可以從執行結果看出,main()主執行緒日誌列印的很快,沒有等待子執行緒列印就結束了。
(2)使用join()方法
程式碼
public class JoinMethodTest {
public static void main(String[] args) {
System.out.println("main thread start");
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("child thread start");
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("child thread finshed");
}
});
thread.start();
//加入join()方法等待子執行緒執行完畢,才執行主執行緒。
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main thread finshed");
}
}
執行結果
main thread start
child thread start
child thread finshed
main thread finshed
從執行結果可以看出,main thread finshed
結果是在最後列印的,加入join()方法等待子執行緒執行完畢,才執行主執行緒。
6種狀態的執行緒生命週期解釋
Q&A
為何啟動執行緒需要用start()方法而不是直接呼叫run()方法?
- 呼叫
start()
方法啟動執行緒,系統會將該執行緒物件的run()
方法當作執行緒執行體來處理。 - 直接呼叫執行緒物件的
run()
方法,該方法會被立即執行,而在run()
方法返回之前其他執行緒無法併發執行(系統會將執行緒物件的當作一個普通物件,將run()
方法當作一個普通方法,而不是執行緒執行體。)
start()方法和run()方法
java Thread中,run方法和start()方法的區別
- 概念:
start()
是啟動執行緒,讓執行緒從新建狀態變為就緒狀態;執行緒得到CPU時間片後,執行run()
中的執行緒執行體; - 呼叫次數:
start()
只能呼叫一次;run()
可以重複呼叫。 - 方法型別:啟動執行緒只能用
start()
,系統會把run()
方法當做執行緒執行體處理;如果直接呼叫run()
,系統會把執行緒物件當作普通物件,此時run()
也是一個普通方法,而不是執行緒執行體。run()方法只是類的一個普通方法而已,如果直接呼叫run方法,程式中依然只有主執行緒這一個執行緒,其程式執行路徑還是隻有一條,還是要順序執行,還是要等待run方法體執行完畢後才可繼續執行下面的程式碼。。 - 原始碼:
start()
原始碼中實際上通過本地方法start0()
啟動執行緒,會新執行一個執行緒,新執行緒會呼叫run()
方法;而run()
原始碼中target
是Runnable物件
,run()
直接呼叫Thread執行緒
的Runnable
成員的run()
方法,並不會新建一個執行緒。 - 多執行緒:用 start方法來啟動執行緒,是真正實現了多執行緒, 通過呼叫Thread類的start()方法來啟動一個執行緒,這時此執行緒處於就緒(可執行)狀態,並沒有執行,一旦得到cpu時間片,就開始執行run()方法。但要注意的是,此時無需等待run()方法執行完畢,即可繼續執行下面的程式碼。所以run()方法並沒有實現多執行緒。
sleep()和yield()方法的區別
- 依賴執行緒優先順序:sleep()方法暫停當前執行緒後,會給其他執行緒執行機會,而不在乎其他執行緒的優先順序;
yield()方法暫停當前執行緒後,只會給優先順序相同或更高的執行緒執行機會。 - 執行緒轉入狀態:sleep()方法將執行緒轉入阻塞狀態,知道經過阻塞時間才會轉入就緒狀態;
yield()方法不會將執行緒轉入阻塞狀態,而是將執行緒轉入就緒狀態。 - 異常宣告:sleep()方法宣告丟擲了InterruptedException異常;
yield()方法未宣告丟擲異常。 - 可移植性: sleep()方法的移植性比yield()方法好,所以一般使用sleep()方法控制併發程式設計。