動態

詳情 返回 返回

深入淺出Java多線程(四):線程狀態 - 動態 詳情

引言


大家好,我是你們的老夥計秀才!今天帶來的是[深入淺出Java多線程]系列的第四篇內容:線程狀態。大家覺得有用請點贊,喜歡請關注!秀才在此謝過大家了!!!

在現代軟件開發中,多線程編程已經成為提升應用程序性能和響應能力的關鍵技術。Java作為一門支持多線程編程的主流語言,其內置的豐富併發庫使得開發者能夠輕鬆創建、管理和協調多個線程以實現高效的併發執行。然而,深入理解和掌握Java線程的工作機制及其狀態變化規律,是編寫出穩定、高效併發程序的前提。

在Java中,一個線程在其生命週期內會經歷一系列的狀態變遷,從剛剛創建但尚未啓動的新建狀態(NEW),到正在運行或等待CPU時間片的就緒/運行狀態(RUNNABLE),再到因爭奪鎖資源而暫時阻塞的BLOCKED狀態,以及因調用等待方法進入等待其他線程喚醒的WAITING或TIMED_WAITING狀態,直至線程執行完畢後的終止狀態(TERMINATED)。這些狀態的準確轉換與管理對於理解線程的行為至關重要,也是排查諸如死鎖、飢餓等問題的根本依據。

例如,在一個多線程環境下,當一個線程嘗試獲取已被其他線程持有的鎖時,它將由RUNNABLE狀態轉變為BLOCKED狀態,如以下代碼片段所示:

synchronized (lock) {
    // 進入同步代碼塊前需獲得鎖,否則線程t2將會被阻塞
    Thread t1 = new Thread(() -> {
        // 持有鎖並執行操作
    });
    t1.start();

    Thread t2 = new Thread(() -> {
        synchronized (lock) { // 線程t2試圖獲取已被t1持有的鎖,因此變為BLOCKED狀態
            // 執行相關操作
        }
    });
    t2.start();
}

同時,Java還提供了中斷機制,通過Thread.interrupt()方法可以設置線程的中斷標誌位,而非直接強制停止線程,這就需要程序員在設計線程任務時關注如何正確響應中斷請求,確保程序能在需要時優雅地關閉線程,如下所示:

Thread thread = new Thread(() -> {
    while (!Thread.currentThread().isInterrupted()) {
        // 業務處理邏輯...
    }
    // 當線程收到中斷信號後退出循環,並進行清理工作
});
thread.start();
// 在某個時刻決定中斷線程
thread.interrupt();

因此,本篇博客將深入剖析Java線程的各種狀態及其轉化過程,結合具體的生活場景及代碼示例,幫助讀者建立起對Java線程狀態全面而直觀的認識,從而更好地駕馭多線程編程,提高併發程序的質量與可維護性。

操作系統線程狀態


在現代操作系統中,線程被視為輕量級進程,它們的狀態與進程狀態有着緊密的對應關係。操作系統中的線程主要有三個基本狀態:就緒狀態、執行狀態和等待狀態。

  1. 就緒狀態(ready)
    在這個狀態下,線程已經準備就緒,具備了運行條件,等待操作系統的CPU調度器為其分配處理器資源。一旦獲得CPU時間片,線程便能立即進入執行狀態。在Java虛擬機(JVM)內部,這一狀態被合併到RUNNABLE狀態中,意味着一個Java線程在JVM層面可能是就緒態或者正在執行。
  2. 執行狀態(running)
    當線程獲得CPU並開始執行其任務時,它處於執行狀態。在這個狀態下,線程會佔用CPU進行計算、讀寫內存等操作。對於Java線程而言,其RUNNABLE狀態同樣包括了線程實際在執行的過程。
  3. 等待狀態(waiting)
    線程由於等待特定事件的發生或等待系統資源(如I/O完成)而暫時放棄CPU使用權,進入等待狀態。例如,在Java中調用Object.wait()方法後,線程將釋放持有的鎖並進入WAITING狀態,直到其他線程通過notify()或notifyAll()將其喚醒:

    synchronized (obj) {
        obj.wait(); // 線程在此處進入等待狀態
    }
    

    另外,當線程因無法獲取所需資源(如互斥鎖)而暫停執行時,它也會進入BLOCKED狀態,這在多線程同步場景中很常見:

    synchronized (lock) {
        // 若lock已被其他線程持有,則新嘗試獲取該鎖的線程會進入BLOCKED狀態
    }
    

綜合來看,操作系統線程的狀態轉換是動態且頻繁發生的,由操作系統內核的調度策略決定。而在Java編程中,雖然直接映射的是Java線程的六種狀態,但其背後仍遵循操作系統線程的基本狀態轉換邏輯,併為開發者提供了更為細緻的控制手段來管理線程生命週期。

Java線程的六種狀態詳解


  1. NEW狀態
    當創建一個Thread對象但尚未調用其start()方法時,線程處於NEW狀態。在這個狀態下,線程並未啓動,僅完成了初始化階段。例如:

    Thread thread = new Thread(() -> {
        // 任務代碼
    });
    System.out.println(thread.getState()); // 輸出 NEW
    

    一旦調用了start()方法,線程的狀態將發生改變,開始執行線程體內的代碼。值得注意的是,同一個線程不能重複調用start()方法,否則會拋出IllegalThreadStateException異常。

  2. RUNNABLE狀態
    RUNNABLE是Java中較為特殊的一個狀態,它涵蓋了傳統操作系統中的就緒和運行兩種狀態。當線程已啓動且CPU調度器為其分配了時間片或線程正在等待系統資源(如I/O操作)時,線程都處於RUNNABLE狀態。在Java虛擬機(JVM)中,這樣的線程既可能實際在執行,也可能隨時準備執行。
  3. BLOCKED狀態
    BLOCKED狀態表示線程因嘗試獲取鎖而被阻塞,暫時無法繼續執行。以下是一個模擬線程爭奪鎖從而進入BLOCKED狀態的例子:

    Object lock = new Object();
    
    Thread t1 = new Thread(() -> {
        synchronized (lock) {
            try {
                Thread.sleep(1000); // 持有鎖並休眠
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
    
    Thread t2 = new Thread(() -> {
        synchronized (lock) { // 嘗試獲取已被t1持有的鎖,因此進入BLOCKED狀態
            // 執行相關操作
        }
    });
    
    t1.start();
    t2.start();
    
  4. WAITING狀態
    當線程調用Object.wait()、Thread.join()或者LockSupport.park()等方法後,主動放棄當前持有的鎖並進入WAITING狀態,此時線程必須由其他線程通過notify()、notifyAll()或LockSupport.unpark()方法喚醒才能恢復到RUNNABLE狀態。
    舉例來説,假設兩個線程間的同步與喚醒過程如下:

    Object monitor = new Object();
    
    Thread waiter = new Thread(() -> {
        synchronized (monitor) {
            try {
                monitor.wait(); // 線程進入WAITING狀態
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
    
    Thread notifier = new Thread(() -> {
        synchronized (monitor) {
            // 做一些操作...
            monitor.notify(); // 喚醒waiter線程
        }
    });
    
    waiter.start();
    notifier.start();
    
  5. TIMED_WAITING狀態
    TIMED_WAITING狀態與WAITING狀態相似,區別在於線程會在指定的時間間隔後自動喚醒,無需其他線程顯式地喚醒它。常見的情況包括使用Thread.sleep(long millis)、Object.wait(long timeout)、Thread.join(long millis)或LockSupport類的相關超時方法。例如:

    Thread t = new Thread(() -> {
        try {
            Thread.sleep(2000); // 線程進入TIMED_WAITING狀態,2秒後自動喚醒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
    t.start();
    
  6. TERMINATED狀態
    當線程正常結束執行,或者因為異常導致線程終止時,線程就會轉為TERMINATED狀態。在Java程序中,可以通過調用Thread.join()方法來等待一個線程完成執行,並觀察其最終狀態:

    Thread task = new Thread(() -> {
        // 任務代碼
    });
    
    task.start();
    task.join(); // 等待task線程執行完畢
    System.out.println(task.getState()); // 輸出 TERMINATED
    

    綜上所述,Java線程的這六種狀態體現了線程生命週期的完整過程,理解這些狀態轉換對於編寫高效的併發程序至關重要。

Java線程狀態之間的轉換過程

  1. BLOCKED與RUNNABLE狀態間的轉換
    在多線程併發環境下,當一個線程嘗試獲取已經被其他線程持有的鎖時,它將從RUNNABLE狀態轉為BLOCKED狀態。例如,在Java的synchronized關鍵字同步塊中,線程競爭鎖資源的情況如下:

     
     ```java
     Object lock = new Object();
     
     Thread threadA = new Thread(() -> {
         synchronized (lock) {
             try {
                 Thread.sleep(5000); // 持有鎖並休眠
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
     });
     
     Thread threadB = new Thread(() -> {
         synchronized (lock) { // 嘗試獲取已被threadA持有的鎖,因此變為BLOCKED狀態
             // 執行相關操作
         }
     });
     
     threadA.start();
     threadB.start();
     
     // 經過一段時間後,打印線程狀態
     while (true) {
         if (threadB.getState() != Thread.State.BLOCKED)
             continue;
         System.out.println("Thread B is now BLOCKED");
         break;
     }
     
     ```
     

    當線程A釋放了對鎖的控制權後,線程B會重新變為RUNNABLE狀態,並有機會獲得CPU時間片執行其代碼。

  2. WAITING狀態與RUNNABLE狀態的轉換
    線程調用Object.wait()方法或Thread.join()方法會進入WAITING狀態,等待被其他線程喚醒。如以下例子所示,線程A在等待線程B結束後才繼續執行:

     
     ```java
     Thread threadB = new Thread(() -> {
         try {
             Thread.sleep(3000); // 線程B執行一段耗時操作
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
     });
     
     Thread threadA = new Thread(() -> {
         try {
             threadB.join(); // 線程A等待線程B結束,此時線程A為WAITING狀態
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println("Thread A resumed after thread B finished.");
     });
     
     threadB.start();
     threadA.start();
     
     ```
     
     當線程B運行完畢後,線程A將由WAITING狀態返回到RUNNABLE狀態,進而得以執行。
     
  3. TIMED_WAITING與RUNNABLE狀態的轉換
    通過調用Thread.sleep(long)、Object.wait(long)、Thread.join(long)等方法,線程可以設定一個超時時間後自動醒來,從而進入TIMED_WAITING狀態。當超時時間到達或者提前被其他線程喚醒時,線程會回到RUNNABLE狀態。

     
     ```java
     Thread threadA = new Thread(() -> {
         try {
             Thread.sleep(2000); // 線程A進入TIMED_WAITING狀態,等待2秒
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println("Thread A back to RUNNABLE state after timeout.");
     });
     
     threadA.start();
     
     ```
     
  4. 線程中斷狀態及其處理
    Java提供了線程中斷機制,允許線程在運行過程中響應中斷請求。當調用Thread.interrupt()方法時,線程不會立即停止執行,而是設置其內部的中斷標誌位。線程可以通過檢查自身的中斷狀態來決定如何響應中斷請求。

     
     ```java
     Thread threadC = new Thread(() -> {
         while (!Thread.currentThread().isInterrupted()) {
             // 執行任務...
         }
         System.out.println("Thread C interrupted and exiting gracefully.");
     });
     
     threadC.start();
     // 在某個時刻決定中斷線程C
     threadC.interrupt();
     
     ```
     
     調用`Thread.interrupted()`會清除當前線程的中斷狀態並返回當前是否處於中斷狀態;而`Thread.isInterrupted()`僅檢查當前線程的中斷狀態而不改變它。在實際應用中,開發者需要設計合理的中斷策略以確保線程能夠正確地處理中斷請求並在適當的時候退出執行。
     

注意事項


在實踐中,應當遵循以下幾點建議:
線程中斷處理:對可中斷的任務,務必檢查並響應中斷請求,例如在循環體內部調用Thread.currentThread().isInterrupted()來判斷並優雅地結束線程執行。

    
    ```java
    while (!Thread.currentThread().isInterrupted()) {
        // 執行任務...
    }
    
    ```

資源協調:充分了解並掌握線程間如何通過鎖、條件變量等手段進行有效的通信和協調,防止長時間的不必要等待造成系統瓶頸。
異常處理:在多線程環境中,任何線程拋出未捕獲的異常都會導致該線程直接轉為TERMINATED狀態,因此要在關鍵代碼段添加合適的異常處理邏輯。
測試驗證:通過編寫單元測試和集成測試,模擬不同場景下的線程狀態轉換,確保線程在各種情況下都能按預期流轉,有效避免併發問題。

總結

總之,理解並熟練運用Java線程的狀態轉換原理是提升併發編程能力的關鍵所在,通過對線程狀態的精細控制,可以打造出更健壯、高效的併發應用程序。

通過深入剖析Java線程的六種狀態及其轉換過程,我們理解到在多線程編程中,合理管理線程狀態對於保證程序正確執行、避免死鎖和資源浪費至關重要。針對每個狀態:

  • NEW:創建線程後應儘快調用start()方法啓動線程,避免出現未初始化的線程實例。
  • RUNNABLE:雖然此狀態表示線程可運行或正在運行,但要注意線程間的同步問題,使用synchronized關鍵字和Lock機制時,可能會導致線程進入BLOCKED狀態。
  • BLOCKED:對共享資源進行同步訪問時,需要確保適時釋放鎖以允許其他等待線程繼續執行,防止因競爭激烈而導致系統性能下降。
  • WAITING/TIMED_WAITING:在設計線程間協作時,應適當利用Object類的wait/notify以及Thread.join等方法,明確設置超時時間,以便線程在合適時機喚醒或者自動返回RUNNABLE狀態。
  • TERMINATED:線程完成任務後應及時處理終止邏輯,如清理資源,並考慮是否需要重新啓動或替換新的工作線程。
user avatar xiaoniuhululu 頭像 u_11365552 頭像 jkdataapi 頭像 huangxunhui 頭像 aipaobudezuoyeben 頭像 tuhooo 頭像 hankin_liu 頭像 changqingdezi 頭像 shenchendexiaoyanyao 頭像 javaedge 頭像 xiongshihubao 頭像 dreamlu 頭像
點贊 29 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.