阿里一面:Java中如何停止執行緒?

码农Academy發表於2024-03-14

引言

在Java多執行緒程式設計中,正確且安全地停止執行緒是一項關鍵技能。簡單粗暴地“殺死”執行緒不僅可能導致資料不一致性,還可能引發各種難以預測的錯誤。本文將探討幾種在Java中優雅地停止執行緒的方法,以確保程式的健壯性和可靠性。

使用標誌位(共享變數)停止執行緒

一種常見的做法是使用一個boolean型別的標誌位來控制執行緒的執行。執行緒在執行任務的過程中不斷檢查標誌位的狀態,當標誌位被設定為true時,執行緒停止執行任務,從而退出執行緒。

class StoppableThread extends Thread {  
    private volatile boolean isStopped = true;  
  
    @Override  
    public void run() {  
        while (isStopped) {  
            // 執行緒執行任務的程式碼  
        }  
        // 清理資源並在檢查到退出標誌時退出  
    }  
  
    public void stopThread() {  
        // 在需要時設定標識=false,停止執行緒   
isStopped = false;  
    }  
}

這種方式簡單易用,它可以控制執行緒的停止時機,靈活性較高,適用於簡單的執行緒任務。但是如果任務執行時間過長或者任務中有阻塞操作,可能無法及時響應停止請求。並且這種方式的任務中需要週期性地檢查標誌位狀態,可能會影響效能。

使用Thread.stop()方法(已過時)

雖然Thread類提供了stop()方法用於停止執行緒,但是這個方法已經被標記為過時(deprecated),不推薦使用。因為它可能會導致執行緒不安全的終止,引發一系列問題,如不釋放鎖、資源洩露等。

image.png

stop()方法確實可以停止執行緒的。

使用interrupt()方法

Java提供了一種基於中斷模型的方式來請求執行緒停止。中斷並不是立即停止執行緒的執行,而是設定一箇中斷標誌,執行緒需要自行檢查並響應這個中斷訊號。

public static void main(String[] args) {  
    // 預設情況下,interrupted標記位為false。該方法用於檢查當前執行緒的中斷狀態,返回true表示執行緒已被中斷,返回false表示執行緒未被中斷  
    System.out.println("初始狀態下的Interrupted標記位:" + Thread.currentThread().isInterrupted());  
    // 執行Interrupted。該方法用於中斷當前執行緒,它會設定當前執行緒的中斷標記位為true。  
    Thread.currentThread().interrupt();  
    System.out.println("執行Interrupted後的Interrupted標記位:" + Thread.currentThread().isInterrupted());  
    // 該方法用於檢查當前執行緒的中斷狀態,並清除中斷狀態。如果當前執行緒被中斷,則返回true,並清除中斷狀態;如果當前執行緒未被中斷,則返回false。  
    System.out.println("返回當前執行緒:" + Thread.interrupted());  
    System.out.println(Thread.interrupted());  
}
初始狀態下的Interrupted標記位:false
執行Interrupted後的Interrupted標記位:true
返回當前執行緒:true
false

在這段程式碼中,兩次呼叫interrupted()方法,第一次返回true(因為之前呼叫了interrupt()方法),第二次返回false(因為在第一次呼叫後,中斷狀態被清除)。

Thread myThread = new Thread(() -> {  
    while (!Thread.currentThread().isInterrupted()) {  
        // 執行任務...  
    }  
    // 清理資源並在檢查到中斷標誌時退出  
});  
  
myThread.start();  
// 在需要停止執行緒時  
myThread.interrupt();

對於阻塞操作(如IO操作或wait()方法),當執行緒在阻塞狀態時收到中斷請求,某些方法會丟擲InterruptedException。在這種情況下,處理方式通常是捕獲異常,執行必要的清理操作,並根據需要決定是否退出執行緒。

try {  
    synchronized (someLock) {  
        someLock.wait();  // 在wait期間,如果執行緒被中斷,會丟擲InterruptedException  
    }  
} catch (InterruptedException e) {  
    Thread.currentThread().interrupt(); // 重新設定中斷標誌,以便後續程式碼可以感知  
    // 清理並退出執行緒  
}

這種方式可以優雅地停止執行緒,即使執行緒處於阻塞狀態也能夠及時響應停止請求。透過捕獲InterruptedException異常,可以在停止執行緒時進行清理工作。但是需要線上程任務中處理InterruptedException異常,這無形之中增加了程式碼複雜度。並且如果任務不是阻塞的,需要在任務中週期性地檢查執行緒的中斷狀態。

使用執行緒池來管理執行緒

使用ExecutorService來管理執行緒可以更加靈活地控制執行緒的生命週期。透過呼叫ExecutorServiceshutdown()shutdownNow()方法來停止執行緒池中的所有執行緒,從而優雅地停止執行緒。

ExecutorService executor = Executors.newFixedThreadPool(5);  
  
// 提交任務到執行緒池  
executor.submit(new MyTask());  
  
// 關閉執行緒池  
executor.shutdown();

透過ExecutorService可以更加靈活地管理執行緒,包括啟動、執行和停止。但是他是停止執行緒池中的所有執行緒。在Java中,沒有直接提供停止執行緒池中特定執行緒的方法。因為執行緒池是一種管理執行緒的容器,它負責管理執行緒的建立、排程和銷燬。但是我們可以使用Future物件來表示執行緒池中的任務,透過呼叫Futurecancel(boolean mayInterruptIfRunning)方法,可以嘗試取消任務的執行。在呼叫cancel(true)時,表示可以中斷正在執行的任務。

ExecutorService executor = Executors.newSingleThreadExecutor();  
Future<?> future = executor.submit(() -> {  
    // 執行任務...  
    // 在適當位置檢查Thread.currentThread().isInterrupted()  
});  
  
// 在需要停止任務時  
future.cancel(true);  // 引數為true表示可以中斷正在執行的任務  
executor.shutdown();

這種方式雖然呼叫了cancel(true),任務也需要在適當的時機檢查中斷狀態並做出相應處理。如果任務已經被中斷,可能無法繼續執行,需要在任務中捕獲InterruptedException異常並進行適當的清理工作。

總結

優雅地停止執行緒是編寫高質量Java多執行緒程式的關鍵之一。在選擇停止執行緒的方法時,需要考慮執行緒的執行情況、任務特性以及程式設計的要求。一般而言,使用interrupt()方法或者ExecutorService來管理執行緒是較為推薦的做法,可以有效地避免執行緒不安全的終止以及資源洩露等問題。

本文已收錄於我的個人部落格:碼農Academy的部落格,專注分享Java技術乾貨,包括Java基礎、Spring Boot、Spring Cloud、Mysql、Redis、Elasticsearch、中介軟體、架構設計、面試題、程式設計師攻略等

相關文章