一、引言
今天同事提了一个问题,每次系统发布的时候在老的节点服务里面的线程池会怎么样,队列里面的任务会被执行完毕吗?
博主一直认为优雅退出一定是会等执行完才退出的,结果跟chatGpt聊了聊,一波三折。
二、分析
1、ThreadPoolExecutor的退出
博主问的是:java服务退出的时候,线程池的队列中的任务会怎么样?
chatGpt回答:当 Java 服务退出时,线程池中尚未执行的任务(队列中的任务)的处理方式取决于线程池的类型和配置。对于标准的 ThreadPoolExecutor
,当 Java 服务退出时,线程池会尝试完成尚未执行的任务。它会等待队列中的任务被执行完毕,并逐个处理每个任务,直到队列为空。这意味着线程池会等待所有已提交的任务完成,然后关闭线程池。
默认情况下,ThreadPoolExecutor
使用的是非守护线程,因此线程池中的线程不会阻止 Java 服务的退出,但会继续执行任务直到队列为空。如果你想要在 Java 服务退出时立即停止线程池中的任务,可以将线程池的线程设置为守护线程(通过设置 setDaemon(true)
),这样当主线程退出时,守护线程会被中断并停止任务的执行。
这么一看,跟博主的想法是一致的,但是博主又想了一个问题,一般ThreadPoolExecutor
都是交给spring托管的,还得考虑一下spring。
@Beanpublic ThreadPoolExecutor threadPoolExecutor() {// 创建自定义的 ThreadPoolExecutor// ...}
2、spring托管ThreadPoolExecutor的退出
博主问的是:Spring Boot不会等@bean定义的ThreadPoolExecutor执行完任务吗?
chatGpt回答的是:在 Spring Boot 中,使用 @Bean
定义的 ThreadPoolExecutor
默认情况下不会等待其中的任务执行完毕。当 Spring Boot 应用程序关闭时,它会触发关闭钩子,自动关闭 Spring 容器中的所有对象,包括通过 @Bean
定义的线程池。
默认情况下,Spring Boot 会调用线程池的 shutdown()
方法来关闭线程池,但不会等待其中的任务全部执行完毕。正在执行的任务会继续执行,而尚未执行的任务将会被丢弃。
这就惊呆博主了,这样搞那不是队列堆的数据全完了,发布一次如果碰到一些流量,流量全是废掉。但是真的会这样吗?
3、shutdownNow与shutdown的区别
既然spring用ThreadPoolExecutor
的shutdown方法关闭的线程池,那么回归源码看看吧,结果一看还有个shutdownNow方法,这明显带了点暴力和优雅的区别。
看看两个方法之间的区别
public void shutdown() {final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {checkShutdownAccess();advanceRunState(SHUTDOWN);interruptIdleWorkers();onShutdown(); // hook for ScheduledThreadPoolExecutor} finally {mainLock.unlock();}tryTerminate();}
public List<Runnable> shutdownNow() {List<Runnable> tasks;final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {checkShutdownAccess();advanceRunState(STOP);interruptWorkers();tasks = drainQueue();} finally {mainLock.unlock();}tryTerminate();return tasks;}
比较大的区别是:
1、shutdownNow会取出任务返回
2、shutdownNow会通过cas设置STOP状态,而shutdown会设置SHUTDOWN状态
很明显区别在这个状态,那么这个状态是不是影响了队列任务呢,看一下获取队列任务方法
private Runnable getTask() {boolean timedOut = false; // Did the last poll() time out?for (;;) {int c = ctl.get();int rs = runStateOf(c);// Check if queue empty only if necessary.if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {decrementWorkerCount();return null;}int wc = workerCountOf(c);// Are workers subject to culling?boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;if ((wc > maximumPoolSize || (timed && timedOut))&& (wc > 1 || workQueue.isEmpty())) {if (compareAndDecrementWorkerCount(c))return null;continue;}try {Runnable r = timed ?workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :workQueue.take();if (r != null)return r;timedOut = true;} catch (InterruptedException retry) {timedOut = false;}}}
很明显华点在于if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())),当STOP的时候一定不会再返回任务给worker,但是SHUTDOWN的时候,队列为空才不返回,因此shutdown方法会继续执行队列中的任务,这也就和chatGpt说的产生了不同的结论!
4、bean的destroyMethod
但是还有个问题,对象的销毁方法,一般是会在对象上指定shutdown
@Bean(name = "handoverExecutor", destroyMethod = "shutdown")public ThreadPoolExecutor handoverExecutor() {return new ThreadPoolExecutor(20, 30,600, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100), new ThreadPoolExecutor.DiscardPolicy());}
但是也有时候会不指定,走默认的,那么默认的是什么呢
String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;public static final String INFER_METHOD = "(inferred)";
这个inferred是什么方法,跟踪一下反射取方法的地方,可以看到没有close就取shutdown方法,而ThreadPoolExecutor是没有close方法的,因此它默认走shutdown
String destroyMethodName = beanDefinition.getDestroyMethodName();if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) ||(destroyMethodName == null && closeableInterface.isInstance(bean))) {// Only perform destroy method inference or Closeable detection// in case of the bean not explicitly implementing DisposableBeanif (!(bean instanceof DisposableBean)) {try {return bean.getClass().getMethod(CLOSE_METHOD_NAME).getName();}catch (NoSuchMethodException ex) {try {return bean.getClass().getMethod(SHUTDOWN_METHOD_NAME).getName();}catch (NoSuchMethodException ex2) {// no candidate destroy method found}}}return null;}return (StringUtils.hasLength(destroyMethodName) ? destroyMethodName : null);
三、总结
chatGpt在单一方面的知识搜索的确是比较深入精准,相对于直接从百度、谷歌、技术社区过滤大量的言论要好很多,可以说是一个优秀的AI搜索引擎。
但是chatGpt的弱点在于不会分析,一旦结合多方面的知识,给出的信息可能是错的,各位同学还是多看权威书籍和源码、技术社区多交流,不用一味相信搜索引擎。