當前位置: 妍妍網 > 碼農

Java執行緒池中執行緒異常後:是銷毀還是復用?

2024-04-01碼農

點選「 IT碼徒 」, 關註,置頂 公眾號

每日技術幹貨,第一時間送達!

「一個執行緒池中的執行緒異常了,那麽執行緒池會怎麽處理這個執行緒?

需要說明,本文的執行緒池都是java.util.concurrent.ExecutorService執行緒池,本文將圍繞驗證,閱讀源碼倆方面來解析這個問題。

1

程式碼驗證

驗證execute送出執行緒池中

測試程式碼:

public classThreadPoolExecutorDeadTest {
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = buildThreadPoolExecutor();
executorService.execute(() -> exeTask("execute"));
executorService.execute(() -> exeTask("execute"));
executorService.execute(() -> exeTask("execute-exception"));
executorService.execute(() -> exeTask("execute"));
executorService.execute(() -> exeTask("execute"));
Thread.sleep(5000);
System.out.println("再次執行任務=======================");
executorService.execute(() -> exeTask("execute"));
executorService.execute(() -> exeTask("execute"));
executorService.execute(() -> exeTask("execute"));
executorService.execute(() -> exeTask("execute"));
executorService.execute(() -> exeTask("execute"));
}
public static ExecutorService buildThreadPoolExecutor() {
returnnew ThreadPoolExecutor(5, 10, 30, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000), new ThreadFactoryBuilder().setNameFormat("test-%s").build()
, new ThreadPoolExecutor.CallerRunsPolicy());
}
private static void exeTask(String name) {
String printStr = "[thread-name:" + Thread.currentThread().getName() + ",執行方式:" + name + "]";
if ("execute-exception".equals(name)) {
thrownew RuntimeException(printStr + ", 我拋異常了");
} else {
System.out.println(printStr);
}
}
}



執行結果如下:

結論:

「execute 送出到執行緒池的方式,如果執行中丟擲異常,並且沒有在執行邏輯中catch,那麽會丟擲異常,並且移除丟擲異常的執行緒,建立新的執行緒放入到執行緒池中。

驗證submit送出執行緒池中

測試程式碼:

public classThreadPoolExecutorDeadTest {
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = buildThreadPoolExecutor();
executorService.submit(() -> exeTask("execute"));
executorService.submit(() -> exeTask("execute"));
executorService.submit(() -> exeTask("execute-exception"));
executorService.submit(() -> exeTask("execute"));
executorService.submit(() -> exeTask("execute"));
Thread.sleep(5000);
System.out.println("再次執行任務=======================");
executorService.submit(() -> exeTask("execute"));
executorService.submit(() -> exeTask("execute"));
executorService.submit(() -> exeTask("execute"));
executorService.submit(() -> exeTask("execute"));
executorService.submit(() -> exeTask("execute"));
}
public static ExecutorService buildThreadPoolExecutor() {
returnnew ThreadPoolExecutor(5, 10, 30, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000), new ThreadFactoryBuilder().setNameFormat("test-%s").build()
, new ThreadPoolExecutor.CallerRunsPolicy());
}
private static void exeTask(String name) {
String printStr = "[thread-name:" + Thread.currentThread().getName() + ",執行方式:" + name + "]";
if ("execute-exception".equals(name)) {
thrownew RuntimeException(printStr + ", 我拋異常了");
} else {
System.out.println(printStr);
}
}
}




執行結果如下:

結論:

「submit 送出到執行緒池的方式,如果執行中丟擲異常,並且沒有catch,不會丟擲異常,不會建立新的執行緒。

2

源碼解析

1、java.util.concurrent.AbstractExecutorService#submit(java.lang.Runnable)

2、檢視execute方法的執行邏輯java.util.concurrent.ThreadPoolExecutor#runWorker

3、java.util.concurrent.ThreadPoolExecutor#processWorkerExit

「可以發現,如果丟擲異常,會移除丟擲異常的執行緒,建立新的執行緒。

4、為什麽submit方法,沒有建立新的執行緒,而是繼續復用原執行緒?

還記得,我們在3.1的時候,發現submit也是呼叫了execute方法,但是在呼叫之前,包裝了一層 RunnableFuture,那一定是在RunnableFuture的實作 FutureTask中有特殊處理了,我們檢視源碼可以發現。

但是,我們透過java.util.concurrent.FutureTask#get(),就可以獲取對應的異常資訊。

3

總結

當一個執行緒池裏面的執行緒異常後:

  • 當執行方式是execute時,可以看到堆疊異常的輸出,執行緒池會把這個執行緒移除掉,並建立一個新的執行緒放到執行緒池中。

  • 當執行方式是submit時,堆疊異常沒有輸出。但是呼叫Future.get()方法時,可以捕獲到異常,不會把這個執行緒移除掉,也不會建立新的執行緒放入到執行緒池中。

  • 以上倆種執行方式,都不會影響執行緒池裏面其他執行緒的正常執行。

    來源:blog.51cto.com/u_15714439/10238518

    END

    PS:防止找不到本篇文章,可以收藏點贊,方便翻閱尋找哦。

    往期推薦