JAVA多線程(三)--線程生命周期
一、線程的狀態
在Java中,線程在創建并啟動后,不是一開始就進入執行狀態,也不是一直處于執行狀態。在線程的生命周期中,它要經過新建(NEW)、就緒(RUNNABLE)、阻塞(BLOCKED)、等待(WAITING)、超時等待(TIME_WAITING)、終止(TERMINATED)六種狀態。線程運行后,CPU會在多條線程間切換,于是線程狀態也會多次在運行和阻塞間切換。

線程狀態枚舉源碼(點擊查看代碼)
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
1、新建狀態(NEW)
當使用new關鍵字創建一個新的線程時,線程就處于新建狀態(NEW)了。此時JVM為其分配內存,并初始化成員變量的值。
2、就緒狀態(RUNNABLE)
當線程對象調用start()方法時,該線程就處于就緒狀態(RUNNABLE)。JVM會為其創建方法調用棧和程序計數器,等待調用運行。
運行狀態(RUNNING) 如果就緒線程獲得了CPU執行,開始執行run()方法的線程執行體,則該線程處于運行狀態(RUNNING),實際在Java源碼中并沒有維護該狀態,它被包含在就緒狀態中。
3、阻塞狀態(BLOCKED)
阻塞狀態(BLOCKED)是指線程由于某種原因放棄了CPU使用權,暫時停止運行。知道線程進入就緒狀態(RUNNABLE),才有機會再次獲得CPU使用權轉到運行狀態(RUNNING)。
阻塞狀態分三種:
- 等待阻塞(o.wait()->等待隊列)
運行狀態的線程執行o.wait()方法,JVM會把該線程放入等待隊列中。 - 同步阻塞(lock->鎖池)
運行狀態的線程在獲取對象的同步鎖時,若該同步鎖在被別的線程使用,那么JVM會把該線程放入鎖池中(lock pool)。 - 其他阻塞(sleep/join)
運行狀態的線程執行Thread.sleep(long ms)、t.join()方法,或發出了I/O請求時,JVM會把該線程置為阻塞狀態。當sleep()超時、join()等待線程終止或超時、I/O請求處理完畢時,線程重新轉入就緒狀態(RUNNABLE)
4、等待(WAITING)
等待狀態,沒有超時時間(無限等待),需要被其他線程或者其他的中斷操作。
執行o.wait()、t.join()、LockSupport.park()
5、超時等待(TIME_WAITING)
與等待不同的是,不是無限等待,超時后自動返回
執行t.sleep(),帶參數的o.wait(long)等可以實現
6、終止狀態(TERMINATED)
線程會已以下三種方式結束,結束后就是終止狀態。
-
正常結束:
run()或call()方法執行完成,線程正常結束。 -
異常結束:
線程拋出一個未捕獲的Exception或Error. -
調用stop()方法:
直接調用線程的stop()方法結束線程。該方法通常容易導致死鎖,不推薦使用。
二、線程基本方法
1、start()
Thread通過start()方法來啟動一個線程,這時此線程處于就緒狀態,并沒有馬上運行。
2、run()
run()方法為線程體,它包含了該線程要執行的內容。當線程進入運行狀態時,開始執行run()方法中的代碼。run()方法代碼執行結束,此線程終止。
3、wait()
wait()方法是Object類中的方法。調用該方法的線程進入WAITING狀態,只有等待其他線程通知或被中斷才會返回。
調用wait()方法后,會釋放對象鎖,進入等待鎖定池,只有針對該對象調用notify()方法后本線程才進入對象鎖定池準備獲取對象鎖進入運行狀態。因此,wait()方法一般被用在同步方法或同步代碼塊中。
4、sleep(long)
sleep(long)方法是Thread類中的方法。sleep(long)方法導致線程進入休眠,進入TIME_WAITING狀態,與wait()方法不同的是,sleep(long)不會釋放對象鎖。
sleep(long)方法會讓線程暫停執行指定的時間,讓出CPU資源,但是它的監控狀態會一直保持,當指定的時間到了會自動恢復就緒狀態。
5、yield()
線程讓步。 yield()方法會使當前線程讓出CPU使用權,讓其他線程重新競爭CPU使用權。一般情況下,優先級高的線程有更高的概率成功競爭到CPU使用權,但這不是絕對的,有的操作系統對線程優先級并不敏感。
6、interrupt()
線程中斷。 中斷一個線程,其本意是給這個線程一個通知信號,會影響這個線程內部的一個中斷標識位。這個線程本身并不會因此而改變狀態(如阻塞、終止等)。
- 調用
interrupt()方法并不會中斷一個線程。運行中的線程并不會因此而終止,只是改變了線程內部維護的中斷標識位而已。 - 若調用
sleep(long)使線程進入TIMED_WAITING狀態,此時調用interrupt()方法,會拋出InterruptedException異常,使線程提前結束TIMED_WAITING狀態。 - 許多申明拋出
InterruptedException異常的方法,拋出異常前,都會清楚中斷標識位,所以拋出異常后,調用isInterrupted()方法將會返回false. - 中斷狀態是線程固有的一個標識位,可以通過此標識位安全的終止線程。通過調用
interrupt()方法,在run()方法內可以判斷t.isInterrupted()的值來優雅的終止線程。
7、join()
等待其他線程終止。 在當前線程中調用一個線程的join()方法,則當前線程轉為阻塞狀態,等到調用join()方法的線程終止,當前線程再由阻塞狀態轉為就緒狀態。
很多情況下,主線程創建了子線程,需要用到子線程的返回結果,也就是主線程需要在子線程結束之后再結束,這時就需要用到join()方法。
System.out.println("主線程執行開始");
MyThread myThread = new MyThread();
myThread.start();
myThread.join();
System.out.println("主線程執行結束");
結果必然為:
主線程執行開始
MyThread running
主線程執行結束
8、notify()
線程喚醒。 notify()是Object類中的方法,喚醒在此對象監視器上等待的單個線程,如果有多個線程都在該對象上等待,則會選擇喚醒其中一個線程,選擇是任意的,并在對實現做出決定時發生。類似的方法還有notifyAll(),喚醒在此監視器上的所有線程。
三、終止線程的方式
1、正常運行結束
程序運行結束,線程自動終止。
2、使用退出標志終止線程
某些線程需要長時間運行,只有滿足某些條件的情況下,才能終止這些線程。可以使用一個變量來控制循環。
// 使用 volatile 關鍵字,保障同一時刻只能有一個線程來修改它的值
public class MyThread extends Thread{
public volatile boolean exit = false;
@Override
public void run(){
while(!exit){
System.out.println("do something");
}
}
}
3、Interrupt方法終止線程
使用interrupt()方法來終止線程,有兩種情況:
-
1、線程處于阻塞狀態。 如使用了sleep、同步鎖的wait,socket中的receiver、accept等方法時會使線程處于阻塞狀態。當調用線程的
interrupt()方法時,會拋出InterruptedException異常。通過代碼捕獲該異常然后break跳出循環狀態,從而結束這個線程的運行。一定要先捕獲異常之后通過break來跳出循環,才能正常結束run方法。 -
2、線程未處于阻塞狀態。 使用
isInterrupted()判斷線程的中斷標志來退出循環。當使用interrupt()方法時,中斷標志就會置 true,和使用自定義的標志來控制循環是一樣的道理。
public class MyThread extends Thread{
@Override
public void run(){
// 非阻塞過程中通過判斷中斷標志來退出
while(!isInterrupted()){
System.out.println("do something");
try {
// 阻塞過程中通過捕獲異常來退出
Thread.sleep(500);
}catch (InterruptedException e){
e.printStackTrace();
break;
}
}
}
}
4、stop方法終止線程(線程不安全)
線程中可以直接使用t.stop()方法來強行終止線程。
不安全原因:當調用stop()方法后,創建子線程的線程就會拋出ThreadDeath的Error錯誤,并且會釋放子線程所持有的所以鎖。一般加鎖的模塊都是為了保護數據的一致性,調用stop()方法后,該線程持有的鎖突然被釋放,那么被保護的數據就有可能出現數據不一致的情況,其他線程再使用這些被破壞的數據時就會出現問題。因此,不推薦使用stop()。
四、守護線程
1、定義: 守護線程是指在程序運行的時候在后臺提供一種通用服務的線程,比如垃圾回收線程就是一個很稱職的守護者,并且這種線程并不屬于程序中不可或缺的部分。因此,當所有的非守護線程結束時,程序也就終止了,同時會殺死進程中的所有守護線程。反過來說,只要任何非守護線程還在運行,程序就不會終止。
2、設置: 可以通過setDaemon(true)來設置線程為守護線程。
setDaemon(true)方法必須要在start()方法之前調用,否則會拋出IllegalThreadStateException異常。不能把正常運行的用戶線程設置為守護線程。- 在守護線程中產生的新線程也是守護線程。
- 守護線程應該永遠不去訪問固有資源,如文件、數據庫,因為它會在任何時候甚至在一個操作的中間發生中斷。
3、生命周期: 守護進程(Daemon)是運行在后臺的一種特殊進程。它獨立于控制終端并且周期性地執行某種任務或等待處理某些發生的事件。也就是說守護線程不依賴于終端,但是依賴于系統,與系統“同生共死”。當 JVM 中所有的線程都是守護線程的時候,JVM 就可以退出了;如果還有一個或以上的非守護線程則 JVM 不會退出。

浙公網安備 33010602011771號