在日常使用线程池的过程中,怎样合理地关闭线程池,最小程度地影响业务,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 方法等待线程池关闭。如果是应用程序停止时关闭线程池,需要注意如果线程池线程是后台线程,则有可能导致关闭异常,在前台线程中调用 shutdown 后,接着调用 awaitTermination 进行等待。
那什么时候可以使用 shutdownNow 呢,如果要快速关闭线程池,用这个方法似乎更优,但是它会中断工作线程,而我们的业务代码不确定哪个地方会阻塞(RPC 调用,加锁等待,读取数据库),很多阻塞代码可能会响应中断,抛出异常,导致任务执行异常,所以慎用。