五、線程的生命周期
線程是一個動態執行的過程,它也有從創建到死亡的過程。
在 Thread 類中,有一個枚舉內部類:
上面的信息以圖片表示如下:
第一張圖:
第二張圖:把等待、計時等待、阻塞看成阻塞一個狀態了
1、新建狀態(new):使用 new 創建一個線程,僅僅只是在堆中分配了內存空間
新建狀態下,線程還沒有調用 start()方法啟動,只是存在一個線程對象而已
Thread t = new Thread();//這就是t線程的新建狀態
2、可運行狀態(runnable):新建狀態調用 start() 方法,進入可運行狀態。而這個又分成兩種狀態,ready 和 running,分別表示就緒狀態和運行狀態
就緒狀態:線程對象調用了 start() 方法,等待 JVM 的調度,(此時該線程並沒有運行)
運行狀態:線程對象獲得 JVM 調度,如果存在多個 CPU,那麽運行多個線程並行運行
註意:線程對象只能調用一次 start() 方法,否則報錯:illegaThreadStateExecptiong
3、阻塞狀態(blocked):正在運行的線程因為某種原因放棄 CPU,暫時停止運行,就會進入阻塞狀態。此時 JVM 不會給線程分配 CPU,知道線程重新進入就緒狀態,才有機會轉到 運行狀態。
註意:阻塞狀態只能先進入就緒狀態,不能直接進入運行狀態
阻塞狀態分為兩種情況:
①、當線程 A 處於可運行狀態中,試圖獲取同步鎖時,卻被 B 線程獲取,此時 JVM 把當前 A 線程放入鎖池中,A線程進入阻塞狀態
②、當線程處於運行狀態時,發出了 IO 請求,此時進入阻塞狀態
4、等待狀態(waiting):等待狀態只能被其他線程喚醒,此時使用的是無參數的 wait() 方法
①、當線程處於運行狀態時,調用了 wait() 方法,此時 JVM 把該線程放入等待池中
5、計時等待(timed waiting):調用了帶參數的 wait(long time)或 sleep(long time) 方法
①、當線程處於運行狀態時,調用了帶參數 wait 方法,此時 JVM 把該線程放入等待池中
②、當前線程調用了 sleep(long time) 方法
6、終止狀態(terminated):通常稱為死亡狀態,表示線程終止
①、正常終止,執行完 run() 方法,正常結束
②、強制終止,如調用 stop() 方法或 destory() 方法
③、異常終止,執行過程中發生異常
下面詳細介紹線程的幾種方法:
1、sleep(long millis)線程休眠:讓執行的線程暫停一段時間,進入計時等待狀態。
static void sleep(long millis):調用此方法後,當前線程放棄 CPU 資源,在指定的時間內,sleep 所在的線程不會獲得可運行的機會,此狀態下的線程不會釋放同步鎖(註意和 wait() 的區別,wait 會放棄 CPU 資源,同時也會放棄 同步鎖)
該方法更多的是用來模擬網絡延遲,讓多線程並發訪問同一資源時的錯誤效果更加明顯。
2、join()聯合線程:表示這個線程等待另一個線程完成後(死亡)才執行,join 方法被調用之後,線程對象處於阻塞狀態。寫在哪個線程中,哪個線程阻塞
這種也稱為聯合線程,就是說把當前線程和當前線程所在的線程聯合成一個線程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
package com.ys.thread;
class Join extends Thread{
@Override
public void run() {
for ( int i = 0 ; i < 10 ;i++){
System.out.println( "播放音樂" +i);
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//創建 join 線程對象
Join joinThread = new Join();
for ( int i = 0 ; i < 10 ; i++){
System.out.println( "玩遊戲" +i);
if (i== 3 ){
joinThread.start();
}
if (i== 5 ){
try {
joinThread.join(); //強制運行 join 線程,知道 join 運行完畢了,main 才有機會運行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
|
結果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
玩遊戲 0
玩遊戲 1
玩遊戲 2
玩遊戲 3
玩遊戲 4
玩遊戲 5
播放音樂 0
播放音樂 1
播放音樂 2
播放音樂 3
播放音樂 4
播放音樂 5
播放音樂 6
播放音樂 7
播放音樂 8
播放音樂 9
玩遊戲 6
玩遊戲 7
玩遊戲 8
玩遊戲 9
|
後臺線程(守護線程):在後臺運行的線程,其目的是為其他線程提供服務,也稱為“守護線程”。
①、JVM 的垃圾回收線程就是守護線程。
②、main 方法是前臺線程,不是後臺線程
1 2 3 4 5 6 7 |
public static void main(String[] args) {
String mainThreadName = Thread.currentThread().getName();
System.out.println(mainThreadName); //main
System.out.println(Thread.currentThread().isDaemon()); //false
}
|
特點:
①、若所有的前臺線程都死亡,則後臺線程自動死亡;
②、前臺線程沒有結束,後臺線程是不會結束的;
③、前臺線程創建的線程是前臺線程,後臺線程創建的線程是後臺線程。
Thread.setDaemon(Boolean on)必須在 start() 的方法前調用。否則會報錯。
線程的優先級:
每個線程都有一個優先級,這有助於 系統確定線程的調動順序。
Java 線程的優先級是一個整數,取值範圍是:1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )
默認情況下,每一個線程都會分配一個優先級 NORM_PRIORITY(5)。
具有較高優先級的線程對程序更重要,並且應該在低優先級的線程之前分配處理器資源。但是,線程優先級不能保證線程執行的順序,而且非常依賴於平臺。
線程禮讓:
yield()方法:表示當前線程對象提示調度器自己願意讓出 CPU 資源,但是調度器可以自由的忽略該提示。
調用該方法後,線程對象進入就緒狀態,所以完全有可能:某個線程調用了 yield() 方法,但是線程調度器又把它調度出來重新執行。
從 Java7 提供的文檔上可以清楚的看出,開發中會很少使用該方法,該方法主要運用於調試或測試,它可能有助於多線程競爭條件下的錯誤重現現象。
sleep() 和 yield() 方法的區別:
①、都能使當前處於運行狀態的線程放棄 CPU資源,把運行的機會給其他線程
②、sleep 方法會給其他線程運行的機會,但是不考慮其他線程優先級的問題;yield 方法會優先給更高優先級的線程運行機會
③、調用 sleep 方法後,線程進入計時等待狀態,調用 yield 方法後,線程進入就緒狀態。
五、線程的生命周期