線程生命周期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()方法控制並發編程。
