如何在销毁Web应用程序中的所有其他bean之前关闭Spring任务执行程序/调度程序池?

时间:2011-07-06 21:10:50

标签: java spring threadpool

在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的线程池上使用depends-on属性,该服务bean具有@Async和@Scheduled注释。好像它们从未执行过,我没有得到上下文初始化错误。我假设带注释的服务bean在某种程度上需要首先初始化这些线程池,如果我使用依赖,我会颠倒顺序并使它们不起作用。

5 个答案:

答案 0 :(得分:52)

两种方式:

  1. 有一个bean工具ApplicationListener<ContextClosedEvent>。在上下文之前调用onApplicationEvent()并销毁所有bean。

  2. 有一个bean工具LifecycleSmartLifecycle。在上下文之前调用stop()并销毁所有bean。

  3. 无论哪种方式,您都可以在bean销毁机制发生之前关闭任务。

    例如:

    @Component
    public class ContextClosedHandler implements ApplicationListener<ContextClosedEvent> {
        @Autowired ThreadPoolTaskExecutor executor;
        @Autowired ThreadPoolTaskScheduler scheduler;
    
        @Override
        public void onApplicationEvent(ContextClosedEvent event) {
            scheduler.shutdown();
            executor.shutdown();
        }       
    }
    

    (编辑:固定方法签名)

答案 1 :(得分:7)

我已添加以下代码来终止您可以使用它的任务。您可以更改重试次数。

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;
}

}

答案 2 :(得分:3)

我在Spring bean中启动的线程有类似的问题。在@PreDestroy方法中调用executor.shutdownNow()之后,这些线程没有正确关闭。因此,对我来说,解决方案是让IO的线程finsih已经启动,并且在调用@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/

答案 3 :(得分:1)

如果它将成为基于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();     

    }

}

答案 4 :(得分:0)

我们可以添加&#34; AwaitTerminationSeconds&#34; taskExecutor和taskScheduler的属性如下所示,

<property name="awaitTerminationSeconds" value="${taskExecutor .awaitTerminationSeconds}" />

<property name="awaitTerminationSeconds" value="${taskScheduler .awaitTerminationSeconds}" />

&#34; waitForTasksToCompleteOnShutdown&#34;的文档属性说,当关闭被称为

&#34; 在正在进行的任务完成时,Spring的容器关闭仍在继续。如果您希望此执行程序在容器的其余部分继续关闭之前阻止并等待任务终止 - 例如为了保持您的任务可能需要的其他资源 - 设置&#34; awaitTerminationSeconds&#34;属性而不是此属性。&#34;

https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/scheduling/concurrent/ExecutorConfigurationSupport.html#setWaitForTasksToCompleteOnShutdown-boolean-

因此,始终建议一起使用waitForTasksToCompleteOnShutdown和awaitTerminationSeconds属性。 awaitTerminationSeconds的值取决于我们的应用程序。