1.線程狀态(生命周期)
一個線程在給定的時間點隻能處于一種狀态。
線程可以有如下6 種狀态:
- New (新建立):未啟動的線程;
- Runnable (可運作):可運作的線程,需要等待作業系統資源;
- Blocked (被阻塞):等待螢幕鎖而被阻塞的線程;
- Waiting (等待):等待喚醒狀态,無限期地等待另一個線程喚醒;
- Timed waiting (計時等待):在指定的等待時間内等待另一個線程執行操作的線程;
- Terminated (被終止):已退出的線程。
要确定一個線程的目前狀态, 可調用getState 方法
線程狀态關系圖
注意:虛線框(全大寫英文)的狀态為Java線程狀态。
2.操作線程狀态
2.1.新建立狀态(NEW)
就是執行個體化線程完成後,未啟動線程的狀态。
可通過三種方式建立線程
- 重寫Thread類run()方法
- 實作Runnable接口
- 實作Callable接口
一個簡單的例子概括三種方式
public class Demo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
/**
* 1.直接重寫run() 或繼承Thread類再重寫run()
*/
Thread thread = new Thread() {
@Override
public void run() {
System.out.println("Thread");
}
};
// 開啟線程
thread.start();
/**
* 2.lambda、内部類或線程類方式實作Runnable接口,實作run()方法
* 再交給Thread 類
*/
Thread runThread = new Thread(() -> {
System.out.println("Runnable");
});
// 開啟線程
runThread.start();
/**
* 3.lambda、内部類或線程類方式實作Callable接口,實作call()方法
* 再交給Thread 類:FutureTask本質也是Runnable實作類
*/
FutureTask<String> futureTask = new FutureTask<String>(() -> {
System.out.println("Callable");
return "CallableThread";
});
Thread callThread = new Thread(futureTask);
// 開啟線程
callThread.start();
// 擷取call()方法的傳回值
String s = futureTask.get();
System.out.println("call()方法的傳回值:"+s);
}
}
不重寫 run() 或 call() 方法直接執行個體化Thread類建立的線程沒有實際意義;
隻有Callable方式建立的線程可以擷取線程的傳回值。
2.2.可運作狀态(RUNNABLE)
該狀态指的是線程執行個體化對象調用start()方法後進入的狀态。線程處于可以運作狀态,如果有處理器等資源,就可以執行程式。
該狀态在作業系統層面包含兩步:線程就緒和線程運作中,但在Java線程狀态中,這兩步都統稱為Runnable(可運作)狀态。
線程由就緒狀态變為運作狀态,重點就看你的線程有沒有搶到CPU資源(CPU時間片),誰搶到就運作,沒搶到就等。因為CPU時間片(執行時間)非常短,大概十幾毫秒,是以線程切換的這個時間是非常短的,就緒狀态變為運作狀态的時間也非常短,在開發時幾乎感覺不到這種狀态的變化,是以在Java中将兩者看作是一個整體,重點關注線程可否運作并差別于其他狀态即可,更進一步簡化線程的開發。如果你的程式要運作很久(比如寫個死循環),在一個CPU時間片内沒有執行完成,那麼你的線程就要搶下一次的CPU時間片,搶到了才可以繼續執行程式,沒搶到那就要繼續搶,直到線程中的程式執行完成。
其實這個場景應該都見到過,例如多個線程執行同一個程式,都将日志列印到同一個檔案時,就會出現不同線程的日志混在了一起的情況,不利于排查問題。解決這種問題常見的方法有:一是分線程列印日志到不同檔案;二是将日志資訊儲存到字元串對象中,在程式的最後将日志資訊一次性列印到檔案。第二種方式就是利用CPU的一個時間片來完成日志資訊的列印。
注意:程式隻能對建立狀态的線程調用start()方法,不要對處于非建立狀态的線程調用start() 方法,這都會引發IllegalThreadStateException異常。
2.3.被阻塞狀态(BLOCKED)
線程處于等待螢幕鎖而被阻塞的狀态。有一個線程擷取了鎖未釋放,其他線程也來擷取,但發現擷取不到鎖也進入了被阻塞狀态。
被阻塞狀态隻存在于多線程并發通路下,差別于後面兩種因線程自己進入”等待“而導緻的阻塞。
進入狀态
- 進入synchronized 代碼塊/方法
- 未擷取到鎖
退出狀态
- 擷取到螢幕鎖
2.4.等待喚醒狀态(WAITING)
整個流程是這樣的:線程在某個對象的同步方法中先擷取到對象鎖;在執行wait方法時,該線程将釋放對象鎖,并且該線程被放入到這個對象的等待隊列;等待另一個線程擷取到同一個對象的鎖,然後通過notify() 或 notifyAll() 方法喚醒對象等待隊列中的線程。
從整個流程可以知道
wait (),notify () 和 notifyAll () 方法需要線上程擷取到鎖的情況下才可以繼續執行,是以這三個方法都需要放在同步代碼塊/方法中執行,否則報異常:java.lang.IllegalMonitorStateException。
在同步代碼塊中,線程進入WAITING 狀态時,鎖會被釋放,不會導緻該線程阻塞。反過來想下,如果鎖沒釋放,那其他線程就沒辦法擷取鎖,也就沒辦法喚醒它。
進入狀态
- object.wait()
- thread.join()
- LockSupport.park()
退出狀态
- object.notify()
- object.notifyall()
- LockSupport.unpark()
2.5.計時等待狀态(TIMED_WAITING)
一般是計時結束就會自動喚醒線程繼續執行後面的程式,對于Object.wait(long) 方法還可以主動通知喚醒。
注意:Thread類下的sleep() 方法可以放在任意地方執行;而wait(long) 方法和wait() 方法一樣,需要放在同步代碼塊/方法中執行,否則報異常:java.lang.IllegalMonitorStateException。
進入狀态
- Thread.sleep(long)
- Object.wait(long)
- Thread.join(long)
- LockSupport.parkNanos(long)
- LockSupport.parkNanos(Object blocker, long nanos)
- LockSupport.parkUntil(long)
- LockSupport.parkUntil(Object blocker, long deadline)
注:blocker 參數為負責此線程駐留的同步對象。
退出狀态
- 計時結束
- LockSupport.unpark(Thread)
- object.notify()
- object.notifyall()
2.6.終止(TERMINATED)
線程執行結束
- run()/call() 執行完成
- stop()線程
- 錯誤或異常>>意外死亡
stop() 方法已棄用。
3.檢視線程的6種狀态
通過一個簡單的例子來檢視線程出現的6種狀态。
案例
public class Demo3 {
private static Object object ="obj";
public static void main(String[] args) throws InterruptedException {
Thread thread0 = new Thread(() -> {
try {
// 被阻塞狀态(BLOCKED)
synchronized (object){
System.out.println("thread0 進入:等待喚醒狀态(WAITING)");
object.wait();
System.out.println("thread0 被解除完成:等待喚醒狀态(WAITING)");
}
System.out.println("thread0 "+Thread.currentThread().getState());
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 新建立狀态(NEW)
System.out.println(thread0.getName()+":"+thread0.getState());
Thread thread1 = new Thread(() -> {
try {
System.out.println("thread1 進入:計時等待狀态(TIMED_WAITING)");
Thread.sleep(2);
System.out.println("thread1 出來:計時等待狀态(TIMED_WAITING)");
} catch (InterruptedException e) {
e.printStackTrace();
}
// 被阻塞狀态(BLOCKED)
synchronized (object){
System.out.println("thread1 解除:等待喚醒狀态(WAITING)");
object.notify();
System.out.println("thread1 解除完成:等待喚醒狀态(WAITING)");
}
System.out.println("thread1 "+Thread.currentThread().getState());
});
// 新建立狀态(NEW)
System.out.println(thread1.getName()+":"+thread1.getState());
printState(thread0);
printState(thread1);
// 可運作狀态(RUNNABLE)
thread0.start();
// 可運作狀态(RUNNABLE)
thread1.start();
}
// 使用獨立線程來列印線程狀态
private static void printState(Thread thread) {
new Thread(()->{
while (true){
System.out.println(thread.getName()+":"+thread.getState());
if (thread.getState().equals(Thread.State.TERMINATED)){
System.out.println(thread.getName()+":"+thread.getState());
break;
}
}
}).start();
}
}
執行結果:簡化後的輸出結果
Thread-0:NEW
Thread-1:NEW
Thread-0:RUNNABLE
Thread-1:RUNNABLE
thread0 進入:等待喚醒狀态(WAITING)
Thread-1:BLOCKED
thread1 進入:計時等待狀态(TIMED_WAITING)
Thread-0:BLOCKED
Thread-0:WAITING
……
Thread-0:WAITING
Thread-1:BLOCKED
Thread-1:TIMED_WAITING
……
Thread-1:TIMED_WAITING
Thread-1:BLOCKED
……
Thread-1:BLOCKED
Thread-0:WAITING
……
Thread-0:WAITING
thread1 出來:計時等待狀态(TIMED_WAITING)
Thread-0:WAITING
Thread-1:BLOCKED
thread1 解除:等待喚醒狀态(WAITING)
Thread-1:BLOCKED
Thread-0:WAITING
Thread-0:BLOCKED
thread1 解除完成:等待喚醒狀态(WAITING)
Thread-1:BLOCKED
thread1 RUNNABLE
Thread-0:BLOCKED
Thread-1:TERMINATED
thread0 被解除完成:等待喚醒狀态(WAITING)
Thread-0:BLOCKED
thread0 RUNNABLE
Thread-0:TERMINATED
最終的執行結果如圖。
注意:因為案例中使用了獨立線程來列印不同線程的狀态,會出現狀态列印稍微延遲的情況。
更多優質文章,請關注WX公衆号:Java全棧布道師