執行緒池關閉的小結

偶尔发呆發表於2024-06-28

在日常使用執行緒池的過程中,怎樣合理地關閉執行緒池,最小程度地影響業務,shutdown 和 shutdownNow 該如何選擇?

 1     public void shutdown() {
 2         final ReentrantLock mainLock = this.mainLock;
 3         mainLock.lock();
 4         try {
 5             checkShutdownAccess();
 6             advanceRunState(SHUTDOWN);
 7             interruptIdleWorkers();
 8             onShutdown(); // hook for ScheduledThreadPoolExecutor
 9         } finally {
10             mainLock.unlock();
11         }
12         tryTerminate();
13     }
 1     public List<Runnable> shutdownNow() {
 2         List<Runnable> tasks;
 3         final ReentrantLock mainLock = this.mainLock;
 4         mainLock.lock();
 5         try {
 6             checkShutdownAccess();
 7             advanceRunState(STOP);
 8             interruptWorkers();
 9             tasks = drainQueue();
10         } finally {
11             mainLock.unlock();
12         }
13         tryTerminate();
14         return tasks;
15     }

對比過後,兩個方法,設定的執行緒池狀態不同,分別為 SHUTDOWN 和 STOP,中斷執行緒的細節不同,分別是中斷 idle 工作執行緒和中斷所有工作執行緒。

怎麼判斷工作執行緒是否空閒呢?原來工作執行緒執行任務的時候,會加鎖,任務執行完成後釋放鎖。

 1     final void runWorker(Worker w) {
 2         Thread wt = Thread.currentThread();
 3         Runnable task = w.firstTask;
 4         w.firstTask = null;
 5         w.unlock(); // allow interrupts
 6         boolean completedAbruptly = true;
 7         try {
 8             while (task != null || (task = getTask()) != null) {
 9                 w.lock();
10                 // If pool is stopping, ensure thread is interrupted;
11                 // if not, ensure thread is not interrupted.  This
12                 // requires a recheck in second case to deal with
13                 // shutdownNow race while clearing interrupt
14                 if ((runStateAtLeast(ctl.get(), STOP) ||
15                      (Thread.interrupted() &&
16                       runStateAtLeast(ctl.get(), STOP))) &&
17                     !wt.isInterrupted())
18                     wt.interrupt();
19                 try {
20                     beforeExecute(wt, task);
21                     try {
22                         task.run();
23                         afterExecute(task, null);
24                     } catch (Throwable ex) {
25                         afterExecute(task, ex);
26                         throw ex;
27                     }
28                 } finally {
29                     task = null;
30                     w.completedTasks++;
31                     w.unlock();
32                 }
33             }
34             completedAbruptly = false;
35         } finally {
36             processWorkerExit(w, completedAbruptly);
37         }
38     }

中斷 idle 執行緒時,嘗試獲取鎖,獲取鎖成功則認定執行緒 idle

 1     private void interruptIdleWorkers(boolean onlyOne) {
 2         final ReentrantLock mainLock = this.mainLock;
 3         mainLock.lock();
 4         try {
 5             for (Worker w : workers) {
 6                 Thread t = w.thread;
 7                 if (!t.isInterrupted() && w.tryLock()) {
 8                     try {
 9                         t.interrupt();
10                     } catch (SecurityException ignore) {
11                     } finally {
12                         w.unlock();
13                     }
14                 }
15                 if (onlyOne)
16                     break;
17             }
18         } finally {
19             mainLock.unlock();
20         }
21     }

工作執行緒 idle,它必然阻塞在 getTask 處,它被中斷後,會根據執行緒池狀態返回 null task,完成工作執行緒的退出。

 1     final void runWorker(Worker w) {
 2         Thread wt = Thread.currentThread();
 3         Runnable task = w.firstTask;
 4         w.firstTask = null;
 5         w.unlock(); // allow interrupts
 6         boolean completedAbruptly = true;
 7         try {
 8             while (task != null || (task = getTask()) != null) {
 9                 w.lock();
10                 // If pool is stopping, ensure thread is interrupted;
11                 // if not, ensure thread is not interrupted.  This
12                 // requires a recheck in second case to deal with
13                 // shutdownNow race while clearing interrupt
14                 if ((runStateAtLeast(ctl.get(), STOP) ||
15                      (Thread.interrupted() &&
16                       runStateAtLeast(ctl.get(), STOP))) &&
17                     !wt.isInterrupted())
18                     wt.interrupt();
19                 try {
20                     beforeExecute(wt, task);
21                     try {
22                         task.run();
23                         afterExecute(task, null);
24                     } catch (Throwable ex) {
25                         afterExecute(task, ex);
26                         throw ex;
27                     }
28                 } finally {
29                     task = null;
30                     w.completedTasks++;
31                     w.unlock();
32                 }
33             }
34             completedAbruptly = false;
35         } finally {
36             processWorkerExit(w, completedAbruptly);
37         }
38     }

總結:

shutdown 無法提交新任務,中斷空閒工作執行緒,等待佇列中的任務執行完畢。

shutdownNow 無法提交新任務,中斷所有執行緒,返回佇列中的任務。

可以看出,shutdown 方法已經足夠優雅了,那麼還有哪些地方需要增強呢?如果執行緒池佇列中堆積的任務太多,導致執行緒池關閉的時間太長,可以手動地將佇列中的任務全部移除(當然前提是可以獲取到佇列的引用),然後再關閉執行緒池;同時還可以結合 awaitTermination 方法等待執行緒池關閉。

那什麼時候可以使用 shutdownNow 呢,如果要快速關閉執行緒池,用這個方法似乎更優,但是它會中斷工作執行緒,而我們的業務程式碼不確定哪個地方會阻塞(RPC 呼叫,加鎖等待,讀取資料庫),很多阻塞程式碼可能會響應中斷,丟擲異常,導致任務執行異常,所以慎用。

相關文章