新聞中心
Java 中實現(xiàn)并發(fā)的主要手段就是多線程。線程是操作系統(tǒng)里的一個概念,Java 語言里的線程本質(zhì)上就是操作系統(tǒng)的線程,它們是一一對應(yīng)的。

創(chuàng)新互聯(lián)主要從事網(wǎng)頁設(shè)計、PC網(wǎng)站建設(shè)(電腦版網(wǎng)站建設(shè))、wap網(wǎng)站建設(shè)(手機版網(wǎng)站建設(shè))、自適應(yīng)網(wǎng)站建設(shè)、程序開發(fā)、網(wǎng)站優(yōu)化、微網(wǎng)站、成都微信小程序等,憑借多年來在互聯(lián)網(wǎng)的打拼,我們在互聯(lián)網(wǎng)網(wǎng)站建設(shè)行業(yè)積累了豐富的成都網(wǎng)站設(shè)計、成都網(wǎng)站建設(shè)、外貿(mào)網(wǎng)站建設(shè)、網(wǎng)站設(shè)計、網(wǎng)絡(luò)營銷經(jīng)驗,集策劃、開發(fā)、設(shè)計、營銷、管理等多方位專業(yè)化運作于一體。
要想整明白操作系統(tǒng)中線程的生命周期,就需要搞懂生命周期各個狀態(tài)之間是如何轉(zhuǎn)換的。
接下來先讓我們了解下操作系統(tǒng)的線程生命周期,進而再去學(xué)習(xí) Java 中線程的生命周期。
操作系統(tǒng)中線程的生命周期
操作系統(tǒng)的線程生命周期基本上可以用下圖這個五態(tài)模型來描述。這五態(tài)分別是:初始狀態(tài)、可運行狀態(tài)、運行狀態(tài)、休眠狀態(tài)和終止?fàn)顟B(tài)。
這五態(tài)模型的詳細(xì)情況如下所示。
1.初始狀態(tài):指的是線程已經(jīng)被創(chuàng)建,但是還不允許分配 CPU 執(zhí)行。這個狀態(tài)屬于編程語言特有的,不過這里所謂的被創(chuàng)建,僅僅是在編程語言層面被創(chuàng)建,而在操作系統(tǒng)層面,真正的線程還沒有創(chuàng)建。
2.可運行狀態(tài):指的是線程可以分配 CPU 執(zhí)行。在這種狀態(tài)下,真正的操作系統(tǒng)線程已經(jīng)被成功創(chuàng)建了,所以可以分配 CPU 執(zhí)行。
3.當(dāng)有空閑的 CPU 時,操作系統(tǒng)會將其分配給一個處于可運行狀態(tài)的線程,被分配到 CPU 的線程的狀態(tài)就轉(zhuǎn)換成了運行狀態(tài)。
4.運行狀態(tài)的線程如果調(diào)用一個阻塞的 API(例如以阻塞方式讀文件)或者等待某個事件(例如條件變量),那么線程的狀態(tài)就會轉(zhuǎn)換到休眠狀態(tài),同時釋放 CPU 使用權(quán),休眠狀態(tài)的線程永遠(yuǎn)沒有機會獲得 CPU 使用權(quán)。當(dāng)?shù)却氖录霈F(xiàn)了,線程就會從休眠狀態(tài)轉(zhuǎn)換到可運行狀態(tài)。
5.線程執(zhí)行完或者出現(xiàn)異常就會進入終止?fàn)顟B(tài),終止?fàn)顟B(tài)的線程不會切換到其他任何狀態(tài),進入終止?fàn)顟B(tài)也就意味著線程的生命周期結(jié)束了。
Java 中把可運行狀態(tài)和運行狀態(tài)合并了,這兩個狀態(tài)在操作系統(tǒng)調(diào)度層面有用,而 JVM 層面不關(guān)心這兩個狀態(tài),因為 JVM 把線程調(diào)度交給操作系統(tǒng)處理了,Java 中還細(xì)化了休眠狀態(tài)等。
Java 中線程的生命周期
接下來來看看 Java 中線程的生命周期,Java 中線程共有六種狀態(tài),分別是:
1.NEW(初始化狀態(tài))
2.RUNNABLE(可運行 / 運行狀態(tài))
3.BLOCKED(阻塞狀態(tài))
4.WAITING(無時限等待)
5.TIMED_WAITING(有時限等待)
6.TERMINATED(終止?fàn)顟B(tài))
在操作系統(tǒng)層面,Java 線程中的 BLOCKED(阻塞狀態(tài))、WAITING(無時限等待)、TIMED_WAITING(有時限等待) 是一種狀態(tài),即休眠狀態(tài)。也就是說只要 Java 線程處于這三種狀態(tài)之一,那么這個線程就永遠(yuǎn)沒有 CPU 的使用權(quán)。
所以 Java 線程的生命周期可以簡化為下圖:
其中,BLOCKED(阻塞狀態(tài))、WAITING(無時限等待)、TIMED_WAITING(有時限等待)可以理解為線程導(dǎo)致休眠狀態(tài)的三種原因。那具體是哪些情形會導(dǎo)致線程從 RUNNABLE 狀態(tài)轉(zhuǎn)換到這三種狀態(tài)呢?而這三種狀態(tài)又是何時轉(zhuǎn)換回 RUNNABLE 的呢?以及 NEW、TERMINATED 和 RUNNABLE 狀態(tài)是如何轉(zhuǎn)換的?
1. RUNNABLE 與 BLOCKED 的狀態(tài)轉(zhuǎn)換
只有一種場景會觸發(fā)這種轉(zhuǎn)換,就是線程等待 synchronized 的隱式鎖。synchronized 修飾的方法、代碼塊同一時刻只允許一個線程執(zhí)行,其他線程只能等待,這種情況下,等待的線程就會從 RUNNABLE 轉(zhuǎn)換到 BLOCKED 狀態(tài)。而當(dāng)?shù)却木€程獲得 synchronized 隱式鎖時,就又會從 BLOCKED 轉(zhuǎn)換到 RUNNABLE 狀態(tài)。
如果你熟悉操作系統(tǒng)線程的生命周期的話,可能會有個疑問:線程調(diào)用阻塞式 API 時,是否會轉(zhuǎn)換到 BLOCKED 狀態(tài)呢?在操作系統(tǒng)層面,線程是會轉(zhuǎn)換到休眠狀態(tài)的,但是在 JVM 層面,Java 線程的狀態(tài)不會發(fā)生變化,也就是說 Java 線程的狀態(tài)會依然保持 RUNNABLE 狀態(tài)。JVM 層面并不關(guān)心操作系統(tǒng)調(diào)度相關(guān)的狀態(tài),因為在 JVM 看來,等待 CPU 使用權(quán)(操作系統(tǒng)層面此時處于可執(zhí)行狀態(tài))與等待 I/O(操作系統(tǒng)層面此時處于休眠狀態(tài))沒有區(qū)別,都是在等待某個資源,所以都?xì)w入了 RUNNABLE 狀態(tài)。
而我們平時所謂的 Java 在調(diào)用阻塞式 API 時,線程會阻塞,指的是操作系統(tǒng)線程的狀態(tài),并不是 Java 線程的狀態(tài)。
2. RUNNABLE 與 WAITING 的狀態(tài)轉(zhuǎn)換
總體來說,有三種場景會觸發(fā)這種轉(zhuǎn)換。
第一種場景,獲得 synchronized 隱式鎖的線程,調(diào)用無參數(shù)的 Object.wait() 方法。
第二種場景,調(diào)用無參數(shù)的 Thread.join() 方法。其中的 join() 是一種線程同步方法,例如有一個線程對象 thread A,當(dāng)調(diào)用 A.join() 的時候,執(zhí)行這條語句的線程會等待 thread A 執(zhí)行完,而等待中的這個線程,其狀態(tài)會從 RUNNABLE 轉(zhuǎn)換到 WAITING。當(dāng)線程 thread A 執(zhí)行完,原來等待它的線程又會從 WAITING 狀態(tài)轉(zhuǎn)換到 RUNNABLE。
第三種場景,調(diào)用 LockSupport.park() 方法。其中的 LockSupport 對象,也許你有點陌生,其實 Java 并發(fā)包中的鎖,都是基于它實現(xiàn)的。調(diào)用 LockSupport.park() 方法,當(dāng)前線程會阻塞,線程的狀態(tài)會從 RUNNABLE 轉(zhuǎn)換到 WAITING。調(diào)用 LockSupport.unpark(Thread thread) 可喚醒目標(biāo)線程,目標(biāo)線程的狀態(tài)又會從 WAITING 狀態(tài)轉(zhuǎn)換到 RUNNABLE。
3. RUNNABLE 與 TIMED_WAITING 的狀態(tài)轉(zhuǎn)換
有五種場景會觸發(fā)這種轉(zhuǎn)換:
- 調(diào)用帶超時參數(shù)的 Thread.sleep(long millis) 方法;
- 獲得 synchronized 隱式鎖的線程,調(diào)用帶超時參數(shù)的 Object.wait(long timeout) 方法;
- 調(diào)用帶超時參數(shù)的 Thread.join(long millis) 方法;
- 調(diào)用帶超時參數(shù)的 LockSupport.parkNanos(Object blocker, long deadline) 方法;
- 調(diào)用帶超時參數(shù)的 LockSupport.parkUntil(long deadline) 方法。
- 這里你會發(fā)現(xiàn) TIMED_WAITING 和 WAITING 狀態(tài)的區(qū)別,僅僅是觸發(fā)條件多了超時參數(shù)。
4. 從 NEW 到 RUNNABLE 狀態(tài)
Java 剛創(chuàng)建出來的 Thread 對象就是 NEW 狀態(tài),而創(chuàng)建 Thread 對象主要有兩種方法。一種是繼承 Thread 對象,重寫 run() 方法。示例代碼如下:
- public class MyThread extends Thread {
- @Override
- public void run() {
- // 線程需要執(zhí)行的代碼
- System.out.println(Thread.currentThread().getName());
- }
- public static void main(String[] args) {
- // 創(chuàng)建線程對象
- MyThread myThread = new MyThread();
- }
- }
另一種是實現(xiàn) Runnable 接口,重寫 run() 方法,并將該實現(xiàn)類作為創(chuàng)建 Thread 對象的參數(shù)。示例代碼如下:
- public class Runner implements Runnable {
- @Override
- public void run() {
- // 線程需要執(zhí)行的代碼
- System.out.println(Thread.currentThread().getName());
- }
- public static void main(String[] args) {
- // 創(chuàng)建線程對象
- Thread thread = new Thread(new Runner());
- }
- }
NEW 狀態(tài)的線程,不會被操作系統(tǒng)調(diào)度,因此不會執(zhí)行。Java 線程要執(zhí)行,就必須轉(zhuǎn)換到 RUNNABLE 狀態(tài)。從 NEW 狀態(tài)轉(zhuǎn)換到 RUNNABLE 狀態(tài)很簡單,只要調(diào)用線程對象的 start() 方法就可以了,示例代碼如下:
- public class Runner implements Runnable {
- @Override
- public void run() {
- // 線程需要執(zhí)行的代碼
- System.out.println(Thread.currentThread().getName());
- }
- public static void main(String[] args) {
- // 創(chuàng)建線程對象
- Thread thread = new Thread(new Runner());
- // 從 NEW 狀態(tài)轉(zhuǎn)換到 RUNNABLE 狀態(tài)
- thread.start();
- }
- }
5. 從 RUNNABLE 到 TERMINATED 狀態(tài)
線程執(zhí)行完 run() 方法后,會自動轉(zhuǎn)換到 TERMINATED 狀態(tài),當(dāng)然如果執(zhí)行 run() 方法的時候異常拋出,也會導(dǎo)致線程終止。有時候我們需要強制中斷 run() 方法的執(zhí)行,例如 run() 方法訪問一個很慢的網(wǎng)絡(luò),我們等不下去了,想終止怎么辦呢?Java 的 Thread 類里面倒是有個 stop() 方法,不過已經(jīng)標(biāo)記為 @Deprecated,所以不建議使用了。正確的姿勢其實是調(diào)用 interrupt() 方法。
java.lang.Thread#stop() 源碼:
- @Deprecated
- public final void stop() {
- SecurityManager security = System.getSecurityManager();
- if (security != null) {
- checkAccess();
- if (this != Thread.currentThread()) {
- security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);
- }
- }
- if (threadStatus != 0) {
- resume();
- }
- stop0(new ThreadDeath());
- }
那 stop() 和 interrupt() 方法的主要區(qū)別是什么呢?
stop() 方法會真的殺死線程,如果線程持有 ReentrantLock 鎖,被 stop() 的線程并不會自動調(diào)用 ReentrantLock 的 unlock() 去釋放鎖,那其他線程就再也沒機會獲得 ReentrantLock 鎖,這實在是太危險了。所以該方法就不建議使用了,類似的方法還有 suspend() 和 resume() 方法,這兩個方法同樣也都不建議使用。
而 interrupt() 方法僅僅是通知線程,線程有機會執(zhí)行一些后續(xù)操作,同時也可以無視這個通知。被 interrupt 的線程,是怎么收到通知的呢?一種是異常,另一種是主動檢測。
當(dāng)線程 A 處于 WAITING、TIMED_WAITING 狀態(tài)時,如果其他線程調(diào)用線程 A 的 interrupt() 方法,會使線程 A 返回到 RUNNABLE 狀態(tài),同時線程 A 的代碼會觸發(fā) InterruptedException 異常。上面我們提到轉(zhuǎn)換到 WAITING、TIMED_WAITING 狀態(tài)的觸發(fā)條件,都是調(diào)用了類似 wait()、join()、sleep() 這樣的方法,我們看這些方法的簽名,發(fā)現(xiàn)都會 throws InterruptedException 這個異常。這個異常的觸發(fā)條件就是:其他線程調(diào)用了該線程的 interrupt() 方法。
當(dāng)線程 A 處于 RUNNABLE 狀態(tài)時,并且阻塞在 java.nio.channels.InterruptibleChannel 上時,如果其他線程調(diào)用線程 A 的 interrupt() 方法,線程 A 會觸發(fā) java.nio.channels.ClosedByInterruptException 這個異常;而阻塞在 java.nio.channels.Selector 上時,如果其他線程調(diào)用線程 A 的 interrupt() 方法,線程 A 的 java.nio.channels.Selector 會立即返回。
上面這兩種情況屬于被中斷的線程通過異常的方式獲得了通知。還有一種是主動檢測,如果線程處于 RUNNABLE 狀態(tài),并且沒有阻塞在某個 I/O 操作上,例如中斷線程 A,這時就得依賴線程 A 主動檢測中斷狀態(tài)了。如果其他線程調(diào)用線程 A 的 interrupt() 方法,那么線程 A 可以通過 isInterrupted() 方法,檢測是不是自己被中斷了。
總結(jié)
本文介紹了操作系統(tǒng)中線程的生命周期,隨后對 Java 中線程的生命周期進行介紹,并對各個狀態(tài)間的轉(zhuǎn)換進行講解。
名稱欄目:Java線程的生老病死
分享路徑:http://fisionsoft.com.cn/article/dhhsjjj.html


咨詢
建站咨詢
