寫在開頭
在前面的幾篇部落格裡,我們學習了Java的多執行緒,包括執行緒的作用、建立方式、重要性等,那麼今天我們就要正式踏入執行緒,去學習更加深層次的知識點了。
第一個需要學的就是執行緒的生命週期,也可以將之理解為執行緒的幾種狀態,以及互相之間的切換,這幾乎是Java多執行緒的面試必考題,每一年都有大量的同學,因為這部分內容回答不夠完美而錯過高薪,今天我們結合原始碼,好好來聊一聊。
執行緒的生命週期
所謂執行緒的生命週期,就是從它誕生到消亡的整個過程,而不同的程式語言,對執行緒生命週期的封裝是不同的,在Java中執行緒整個生命週期被分為了六種狀態,我們下面就來一起學習一下。
執行緒的6種狀態
對於Java中執行緒的狀態劃分,我們其實要從兩個方面去看,一是JVM層面,這是我們程式執行的核心,另一層面是作業系統層面,這是我們JVM能夠執行的核心。為了更直觀的分析,build哥列了一個對比圖:
在作業系統層面,對於RUNNABLE狀態拆分為(READY、RUNNING),那為什麼在JVM層面沒有分這麼細緻呢?
這是因為啊,在當下時分多工作業系統架構下,執行緒的驅動是透過獲取CPU時間片,而每個時間片的間隔非常之短(10-20ms),這就意味著一個執行緒在cpu上執行一次的時間在0.01秒,隨後CPU執行權就會發生切換,在如此高頻的切換下,JVM就沒必要去區分READY和RUNNING了。
在Java的原始碼中也可以看到,確實只分了6種狀態:
【原始碼解析1】
// Thread.State 原始碼
public enum State {
//省略了每個列舉值上的註釋
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
NEW(初始化狀態)
我們透過new一個Thread物件,進行了初始化工作,這時的執行緒還沒有被啟動。
【程式碼示例1】
public class Test {
public static void main(String[] args) {
//lambda 表示式
Thread thread = new Thread(() -> {});
System.out.println(thread.getState());
}
}
//執行結果:NEW
我們透過thread.getState()方法去獲得當前執行緒所處在的狀態,此時輸出為NEW。
RUNNABLE(可執行狀態)
對於這種狀態的描述,我們來看一下Thread原始碼中如何說的:
/**
* 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狀態下,代表它可能正處於執行狀態,或者正在等待CPU資源的分配。
那麼我們怎樣從NEW狀態變為RUNNABLE呢?答案很簡單,我們只需要呼叫start()方法即可!
【程式碼示例2】
public class Test {
public static void main(String[] args) {
//lambda 表示式
Thread thread = new Thread(() -> {});
thread.start();
System.out.println(thread.getState());
}
}
//執行結果:RUNNABLE
BLOCKED(阻塞狀態)
當執行緒執行緒進入 synchronized 方法/塊或者呼叫 wait 後(被 notify)重新進入 synchronized 方法/塊,但是鎖被其它執行緒佔有,這個時候執行緒就會進入 BLOCKED(阻塞) 狀態。這時候只有等到鎖被另外一個執行緒釋放,重新獲取鎖後,阻塞狀態解除!
WAITING(無限時等待)
當透過程式碼將執行緒轉為WAITING狀態後,這種狀態不會自動切換為其他狀態,是一種無限時狀態,直到整個執行緒接收到了外界通知,去喚醒它,才會從WAITING轉為uRUNNABLE。
呼叫下面這 3 個方法會使執行緒進入等待狀態:
Object.wait()
:使當前執行緒處於等待狀態直到另一個執行緒喚醒它;Thread.join()
:等待執行緒執行完畢,底層呼叫的是 Object 的 wait 方法;LockSupport.park()
:除非獲得呼叫許可,否則禁用當前執行緒進行執行緒排程。
TIMED_WAITING(有限時等待)
與WAITING相比,TIMED_WAITING是一種有限時的狀態,可以透過設定等待時間,沒有外界干擾的情況下,達到指定等待時間後,自動終止等待狀態,轉為RUNNABLE狀態。
呼叫如下方法會使執行緒進入超時等待狀態:
Thread.sleep(long millis)
:使當前執行緒睡眠指定時間;Object.wait(long timeout)
:執行緒休眠指定時間,等待期間可以透過notify()/notifyAll()喚醒;Thread.join(long millis)
:等待當前執行緒最多執行 millis 毫秒,如果 millis 為 0,則會一直執行;LockSupport.parkNanos(long nanos)
: 除非獲得呼叫許可,否則禁用當前執行緒進行執行緒排程指定時間;LockSupport.parkUntil(long deadline)
:同上,也是禁止執行緒進行排程指定時間;
TERMINATED(終止狀態)
執行緒正常執行結束,或者異常終止,會轉變到 TERMINATED 狀態。
執行緒狀態的切換
上面的6種狀態隨著程式的執行,程式碼(方法)的執行,上下文的切換,也伴隨著狀態的轉變。
NEW 到 RUNNABLE 狀態
這一種轉變比較好理解,我們透過new,初始化一個Thread物件後,這時就是處於執行緒的NEW狀態,此時執行緒是不會獲取CPU時間片排程執行的,只有在呼叫了start()方法後,執行緒徹底建立完成,進入RUNNABLE狀態,等待作業系統排程執行!這種狀態是NEW -> RUNNABLE的單向轉變。
RUNNABLE 與 BLOCKED 的狀態轉變
synchronized 修飾的方法、程式碼塊同一時刻只允許一個執行緒執行,其他執行緒只能等待,等待的執行緒會從 RUNNABLE 轉變到 BLOCKED 狀態,當等待的執行緒獲得 synchronized 隱式鎖時,就又會從 BLOCKED 轉變到 RUNNABLE 狀態。我們透過一段程式碼示例看一下:
【程式碼示例3】
public class Test {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
testMethod();
});
Thread thread2 = new Thread(() -> {
testMethod();
});
thread1.start();
thread2.start();
System.out.println(thread1.getName()+":"+thread1.getState());
System.out.println(thread2.getName()+":"+thread2.getState());
}
// 同步方法爭奪鎖
private static synchronized void testMethod() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
輸出:
Thread-0:RUNNABLE
Thread-1:BLOCKED
程式碼中在主執行緒中建立了2個執行緒,執行緒中都呼叫了同步方法,隨後去啟動執行緒,因為CPU的執行效率較高,還沒阻塞已經完成的列印,所以大部分時間裡會輸出兩執行緒均為RUNNABLE狀態;
當CPU效率稍低時,就會呈現上述結果,thread1啟動後進入RUNNABLE狀態,並且獲得了同步方法,這是thread2啟動後,呼叫的同步方法鎖已經被佔用,它作為等待的執行緒會從 RUNNABLE 轉變到 BLOCKED 狀態,待到thread1同步方法執行完畢,釋放synchronized鎖後,thread2獲得鎖,從BLOCKED轉為RUNNABLE狀態。
RUNNABLE 與 WAITING 的狀態轉變
1、獲得 synchronized 隱式鎖的執行緒,呼叫無引數的 Object.wait() 方法,狀態會從 RUNNABLE 轉變到 WAITING;呼叫 Object.notify()、Object.notifyAll() 方法,執行緒可能從 WAITING 轉變到 RUNNABLE 狀態。
2、呼叫無引數的 Thread.join() 方法。join() 是一種執行緒同步方法,如有一執行緒物件 Thread t,當呼叫 t.join() 的時候,執行程式碼的執行緒的狀態會從 RUNNABLE 轉變到 WAITING,等待 thread t 執行完。當執行緒 t 執行完,等待它的執行緒會從 WAITING 狀態轉變到 RUNNABLE 狀態。
3、呼叫 LockSupport.park() 方法,執行緒的狀態會從 RUNNABLE 轉變到 WAITING;呼叫 LockSupport.unpark(Thread thread) 可喚醒目標執行緒,目標執行緒的狀態又會從 WAITING 轉變為 RUNNABLE 狀態。
RUNNABLE 與 TIMED_WAITING 的狀態轉變
這種與上面的很相似,只是在方法呼叫和引數上有細微差別,因為,TIMED_WAITING 和 WAITING 狀態的區別,僅僅是呼叫的是超時引數的方法。
轉變方法在上文中已經提到了,這裡以sleep(time)為例,寫一個測試案例:
【程式碼示例4】
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
testMethod();
});
Thread thread2 = new Thread(() -> {
// testMethod();
});
thread1.start();
Thread.sleep(1000L);
thread2.start();
System.out.println(thread1.getName()+":"+thread1.getState());
System.out.println(thread2.getName()+":"+thread2.getState());
}
// 同步方法爭奪鎖
private static synchronized void testMethod() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
輸出:
Thread-0:TIMED_WAITING
Thread-1:TERMINATED
這裡面我們啟動threa1後,讓主執行緒休眠了1秒,這時thread1獲得同步方法後,方法內部執行了休眠2秒的操作,因此它處於TIMED_WAITING狀態,而thread2正常執行結束,狀態處於TERMINATED(這個案例同樣可以印證下面RUNNABLE到TERMINATED的轉變)。
RUNNABLE 到 TERMINATED 狀態
轉變為TERMINATED狀態,表明這個執行緒已經執行完畢,通常用如下幾種情況:
- 執行緒執行完 run() 方法後,會自動轉變到 TERMINATED 狀態;
- 執行 run() 方法時異常丟擲,也會導致執行緒終止;
- Thread類的 stop() 方法已經不建議使用。
總結
今天關於執行緒的6種狀態就講到這裡啦,這是個重點知識點,希望大家能夠銘記於心呀!
結尾彩蛋
如果本篇部落格對您有一定的幫助,大家記得留言+點贊+收藏呀。原創不易,轉載請聯絡Build哥!
如果您想與Build哥的關係更近一步,還可以關注“JavaBuild888”,在這裡除了看到《Java成長計劃》系列博文,還有提升工作效率的小筆記、讀書心得、大廠面經、人生感悟等等,歡迎您的加入!