在我們稍稍脫離初學程式的階段時,可能會開始使用許多框架工具,抑或是寫WebApp時使用Servlet API,都會發現去了解架構是很重要的事情,了解架構才有辦法讓我們開發軟體的時候知道自己在哪裡,其中有一部分是去了解軟體的生命週期。
我覺得這是滿有趣的一件事,軟體這種純粹人工的事物理應沒有生命,但在我們人類的理解上,生命週期是最恰當的詞語來形容了。
今天就要來談談執行緒(Thread)的生命週期(Life Cycle)。
我個人在初學Java時,學到執行緒真的是滿頭痛的,不是很能在腦海中有明確的畫面去想像,不過在了解到執行緒的生命週期後,讓我有豁然開朗的感覺。
來上圖!
當我們實作Runnable介面或繼承Thread類別,覆寫run()方法後,就要創建Thread實例來啟動該執行緒:
Thread thread01 = new Thread(() -> {
for(int i = 0; i < 10; i++){
System.out.println(i);
}
});
thread01.start();
當呼叫了Thread的start()後,就是開始了執行緒的生命週期了,thread會進到Runnable的狀態,這個階段就像是被放進一個等待區一樣,而電腦的CPU就像是工作人員會來叫號,被叫到的thread就可以離開等待區,進到工作區占用電腦CPU資源開始跑run()方法;要注意這邊的叫號是隨機的,或者可以透過Java API提供的setPriority()方法設定執行緒被叫號的優先權。
當執行緒的run()方法內容執行完畢後,thread就會進入Terminated的狀態,相當於這個執行緒的生命到了盡頭,就算在thread.start()一遍也沒辦法再跑一次,甚至還會吐出IllegalThreadStateException例外。
除了Runnable以及Running的階段外,有些情況執行緒會進到類似隔離區的Blocked狀態,通常是當執行緒在跑輸入輸出的程式時就會進到Blocked狀態,等輸入輸出完成後才會重新回到等待區Runnable等待CPU叫號;也可能是我們主動在某個流程中呼叫執行緒的sleep()方法,就會強制該執行緒進到Blocked狀態;也可能是在設定了synchronized的程式中等待物件內部鎖(intrinsic lock),也會進到Blocked狀態。
以上是執行緒生命週期的整體概念,下面接著要來說說程式碼上執行緒的執行狀況。
public static void Main(String[] args){
Thread thread01 = new Thread(() -> {
for(int i = 0; i < 10; i++){
System.out.println("thread01 " + i);
}
});
Thread thread02 = new Thread(() -> {
for(int i = 0; i < 10; i++){
System.out.println("thread02 " + i);
}
});
thread01.setDaemon(true);
thread02.start();
thread01.start();
}
我們初學常常測試程式碼使用的Main方法,其實本身就是一個thread,稱為main thread;當Main方法執行完時,main thread也就Terminated了,JVM隨之關閉。
而當我們在Main方法中去啟動其他執行緒時,我們的main thread會等到這些執行緒都執行完畢後,才會進到Terminated狀態;不過有一種設定叫做Daemon,如上面的程式碼範例,我們設定了thread01.setDaemon(true),被設定為Daemon的執行緒不會被main thread等待,main thread跑完了就會直接進到Terminated狀態,所以上面的程式碼跑完會發現thread01有時候會跑不到9,但是thread02一定會跑到9。