[英]How can I shutdown Spring task executor/scheduler pools before all other beans in the web app are destroyed?
在 Spring web 应用程序中,我有几个 DAO 和服务层 bean。 一个服务层 bean 已注释 @Async / @Scheduled 方法。 这些方法依赖于其他(自动装配的)bean。 我在XML中配置了两个线程池:
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="2" />
<property name="maxPoolSize" value="5" />
<property name="queueCapacity" value="5" />
<property name="waitForTasksToCompleteOnShutdown" value="true" />
<property name="rejectedExecutionHandler">
<bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy"/>
</property>
</bean>
<bean id="taskScheduler" class="org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler">
<property name="poolSize" value="10" />
<property name="waitForTasksToCompleteOnShutdown" value="true" />
<property name="rejectedExecutionHandler">
<bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy"/>
</property>
</bean>
<task:annotation-driven executor="taskExecutor" scheduler="taskScheduler"/>
一切都按预期工作。 我的问题是我无法彻底关闭任务池以正常工作。 这些任务在数据库和文件系统上运行。 当我停止 web 应用程序时,它需要一些时间才能停止。 这表明waitForTasksToCompleteOnShutdown
属性有效。 但是,我在日志中得到 IllegalStateExceptions ,表明一些 bean 已经被销毁,但一些工作任务线程仍在执行,它们失败,因为它们的依赖关系被破坏了。
有一个可能相关的 JIRA 问题: SPR-5387
我的问题是:有没有办法告诉 Spring 最后初始化任务执行程序/调度程序 bean,或者有没有办法告诉 Spring 先销毁它们?
我的理解是破坏以相反的初始化顺序发生。 因此,最后初始化的 bean 将首先被销毁。 如果线程池 bean 首先被销毁,所有当前正在执行的任务都将完成并且仍然可以访问依赖的 bean。
我还尝试在引用我的服务 bean 的线程池上使用依赖属性,该服务 bean 具有 @Async 和 @Scheduled 注释。 似乎它们从未执行过,并且我没有收到上下文初始化错误。 我假设带注释的服务 bean 不知何故需要首先初始化这些线程池,如果我使用依赖,我会颠倒顺序并使它们不起作用。
两种方式:
有一个 bean 实现ApplicationListener<ContextClosedEvent>
。 onApplicationEvent()
将在上下文和所有 bean 被销毁之前被调用。
有一个 bean 实现Lifecycle或SmartLifecycle 。 stop()
将在上下文之前被调用,并且所有 bean 都被销毁。
无论哪种方式,您都可以在 bean 销毁机制发生之前关闭任务内容。
例如:
@Component
public class ContextClosedHandler implements ApplicationListener<ContextClosedEvent> {
@Autowired ThreadPoolTaskExecutor executor;
@Autowired ThreadPoolTaskScheduler scheduler;
@Override
public void onApplicationEvent(ContextClosedEvent event) {
scheduler.shutdown();
executor.shutdown();
}
}
(编辑:固定方法签名)
我添加了下面的代码来终止您可以使用它的任务。 您可以更改重试次数。
package com.xxx.test.schedulers;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.apache.log4j.Logger;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Component;
import com.xxx.core.XProvLogger;
@Component
class ContextClosedHandler implements ApplicationListener<ContextClosedEvent> , ApplicationContextAware,BeanPostProcessor{
private ApplicationContext context;
public Logger logger = XProvLogger.getInstance().x;
public void onApplicationEvent(ContextClosedEvent event) {
Map<String, ThreadPoolTaskScheduler> schedulers = context.getBeansOfType(ThreadPoolTaskScheduler.class);
for (ThreadPoolTaskScheduler scheduler : schedulers.values()) {
scheduler.getScheduledExecutor().shutdown();
try {
scheduler.getScheduledExecutor().awaitTermination(20000, TimeUnit.MILLISECONDS);
if(scheduler.getScheduledExecutor().isTerminated() || scheduler.getScheduledExecutor().isShutdown())
logger.info("Scheduler "+scheduler.getThreadNamePrefix() + " has stoped");
else{
logger.info("Scheduler "+scheduler.getThreadNamePrefix() + " has not stoped normally and will be shut down immediately");
scheduler.getScheduledExecutor().shutdownNow();
logger.info("Scheduler "+scheduler.getThreadNamePrefix() + " has shut down immediately");
}
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Map<String, ThreadPoolTaskExecutor> executers = context.getBeansOfType(ThreadPoolTaskExecutor.class);
for (ThreadPoolTaskExecutor executor: executers.values()) {
int retryCount = 0;
while(executor.getActiveCount()>0 && ++retryCount<51){
try {
logger.info("Executer "+executor.getThreadNamePrefix()+" is still working with active " + executor.getActiveCount()+" work. Retry count is "+retryCount);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(!(retryCount<51))
logger.info("Executer "+executor.getThreadNamePrefix()+" is still working.Since Retry count exceeded max value "+retryCount+", will be killed immediately");
executor.shutdown();
logger.info("Executer "+executor.getThreadNamePrefix()+" with active " + executor.getActiveCount()+" work has killed");
}
}
@Override
public void setApplicationContext(ApplicationContext context)
throws BeansException {
this.context = context;
}
@Override
public Object postProcessAfterInitialization(Object object, String arg1)
throws BeansException {
return object;
}
@Override
public Object postProcessBeforeInitialization(Object object, String arg1)
throws BeansException {
if(object instanceof ThreadPoolTaskScheduler)
((ThreadPoolTaskScheduler)object).setWaitForTasksToCompleteOnShutdown(true);
if(object instanceof ThreadPoolTaskExecutor)
((ThreadPoolTaskExecutor)object).setWaitForTasksToCompleteOnShutdown(true);
return object;
}
}
我在 Spring bean 中启动的线程有类似的问题。 在我在 @PreDestroy 方法中调用 executor.shutdownNow() 后,这些线程没有正确关闭。 所以对我来说,解决方案是让 IO 的线程已经启动,并且在调用 @PreDestroy 后不再启动 IO。 这是@PreDestroy 方法。 对于我的应用程序,等待 1 秒是可以接受的。
@PreDestroy
public void beandestroy() {
this.stopThread = true;
if(executorService != null){
try {
// wait 1 second for closing all threads
executorService.awaitTermination(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
在这里,我解释了尝试关闭线程时面临的所有问题。 http://programtalk.com/java/executorservice-not-shutting-down/
如果它是一个基于 web 的应用程序,您也可以使用 ServletContextListener 接口。
public class SLF4JBridgeListener implements ServletContextListener {
@Autowired
ThreadPoolTaskExecutor executor;
@Autowired
ThreadPoolTaskScheduler scheduler;
@Override
public void contextInitialized(ServletContextEvent sce) {
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
scheduler.shutdown();
executor.shutdown();
}
}
我们可以为 taskExecutor 和 taskScheduler 添加“AwaitTerminationSeconds”属性,如下所示,
<property name="awaitTerminationSeconds" value="${taskExecutor .awaitTerminationSeconds}" />
<property name="awaitTerminationSeconds" value="${taskScheduler .awaitTerminationSeconds}" />
“waitForTasksToCompleteOnShutdown”属性的文档说,当调用关闭时
" Spring 的容器关闭在正在进行的任务完成时继续。如果您希望此执行程序在容器的 rest 继续关闭之前阻止并等待任务终止 - 例如,为了保持您的任务可能需要的其他资源-,设置“awaitTerminationSeconds”属性来代替或附加此属性。 ”
因此,始终建议同时使用 waitForTasksToCompleteOnShutdown 和 awaitTerminationSeconds 属性。 awaitTerminationSeconds 的值取决于我们的应用程序。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.