掃二維碼與項目經(jīng)理溝通
我們在微信上24小時期待你的聲音
解答本文疑問/技術咨詢/運營咨詢/技術建議/互聯(lián)網(wǎng)交流
Java 中實現(xiàn)并發(fā)的主要手段就是多線程。線程是操作系統(tǒng)里的一個概念,Java 語言里的線程本質上就是操作系統(tǒng)的線程,它們是一一對應的。

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

我們在微信上24小時期待你的聲音
解答本文疑問/技術咨詢/運營咨詢/技術建議/互聯(lián)網(wǎng)交流