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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6603051/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-16 08:07:26  来源:igfitidea点击:

How can I shutdown Spring task executor/scheduler pools before all other beans in the web app are destroyed?

javaspringthreadpool

提问by tvirtualw

In a Spring web application I have several DAO and service layer beans. One service layer bean has annotated @Async / @Scheduled methods. These methods depend on other (autowired) beans. I have configured two thread pools in XML:

在 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"/>

Everything works as expected. My problem is that I cannot get a clean shutdown of the task pools to work. The tasks operate on the database and on the file system. When I stop the web application it takes some time until it is stopped. This indicates that the waitForTasksToCompleteOnShutdownproperty works. However, I get IllegalStateExceptions in the log indicating that some beans are already destroyed but some worker task threads are still executing and they fail because their dependencies are destroyed.

一切都按预期工作。我的问题是我无法完全关闭任务池以使其正常工作。这些任务在数据库和文件系统上运行。当我停止 Web 应用程序时,它需要一些时间才能停止。这表明该waitForTasksToCompleteOnShutdown属性有效。但是,我在日志中收到 IllegalStateExceptions,表明某些 bean 已经被销毁,但某些工作任务线程仍在执行,并且由于它们的依赖关系被破坏而失败。

There is a JIRA issue which might be relevant: SPR-5387

有一个可能相关的 JIRA 问题:SPR-5387

My question is: Is there a way to tell Spring to initialize the task executor/scheduler beans last or is there a way to tell Spring to destroy them first?

我的问题是:有没有办法告诉 Spring 最后初始化任务执行程序/调度程序 bean,或者有没有办法告诉 Spring 先销毁它们?

My understanding is that destruction takes place in reversed init order. Therefore the bean init'ed last will be destroyed first. If the thread pool beans are destroyed first, all currently executing tasks would finish and could still access dependent beans.

我的理解是破坏以相反的初始化顺序发生。因此最后初始化的 bean 将首先被销毁。如果先销毁线程池 bean,则所有当前正在执行的任务都将完成并且仍然可以访问依赖 bean。

I have also tried using the depends-on attribute on the thread pools referring to my service bean which has the @Async and @Scheduled annotations. Seems like they are never executed then and I do not get context initialization errors. I assume the annotated service bean somehow needs these thread pools initialized first and if I use depends-on I reverse the order and make them non-functional.

我还尝试在引用我的服务 bean 的线程池上使用depends-on 属性,它具有@Async 和@Scheduled 注释。似乎它们从来没有被执行过,我也没有收到上下文初始化错误。我假设带注释的服务 bean 以某种方式需要首先初始化这些线程池,如果我使用依赖,我会颠倒顺序并使它们不起作用。

采纳答案by sourcedelica

Two ways:

两种方式:

  1. Have a bean implement ApplicationListener<ContextClosedEvent>. onApplicationEvent()will get called before the context and all the beans are destroyed.

  2. Have a bean implement Lifecycleor SmartLifecycle. stop()will get called before the context and all the beans are destroyed.

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

  2. 让 bean 实现LifecycleSmartLifecyclestop()将在上下文之前被调用并且所有 bean 都被销毁。

Either way you can shut down the task stuff before the bean destroying mechanism takes place.

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

Eg:

例如:

@Component
public class ContextClosedHandler implements ApplicationListener<ContextClosedEvent> {
    @Autowired ThreadPoolTaskExecutor executor;
    @Autowired ThreadPoolTaskScheduler scheduler;

    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        scheduler.shutdown();
        executor.shutdown();
    }       
}

(Edit: Fixed method signature)

(编辑:固定方法签名)

回答by fatih tekin

I have added below code to terminate tasks you can use it. You may change the retry numbers.

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

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

}

}

回答by Balasubramanian Jayaraman

If it is going to be a web based application, you can also use the ServletContextListener interface.

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

    }

}

}

回答by awsome

I had similar issues with the threads being started in Spring bean. These threads were not closing properly after i called executor.shutdownNow() in @PreDestroy method. So the solution for me was to let the thread finsih with IO already started and start no more IO, once @PreDestroy was called. And here is the @PreDestroy method. For my application the wait for 1 second was acceptable.

我在 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();
            }
        }
    }

Here I have explained all the issues faced while trying to close threads.http://programtalk.com/java/executorservice-not-shutting-down/

在这里,我已经解释了尝试关闭线程时面临的所有问题。http://programtalk.com/java/executorservice-not-shutting-down/

回答by Manjunath D R

We can add "AwaitTerminationSeconds" property for both taskExecutor and taskScheduler as below,

我们可以为 taskExecutor 和 taskScheduler 添加“AwaitTerminationSeconds”属性,如下所示,

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

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

Documentation for "waitForTasksToCompleteOnShutdown" property says, when shutdown is called

“waitForTasksToCompleteOnShutdown”属性的文档说,当调用关闭时

"Spring's container shutdown continues while ongoing tasks are being completed. If you want this executor to block and wait for the termination of tasks before the rest of the container continues to shut down - e.g. in order to keep up other resources that your tasks may need -, set the "awaitTerminationSeconds" property instead of or in addition to this property."

" Spring 的容器关闭会在正在进行的任务完成时继续。如果您希望此执行器在容器的其余部分继续关闭之前阻塞并等待任务终止 - 例如,为了保持您的任务可能需要的其他资源-,设置“awaitTerminationSeconds”属性来代替或添加到此属性。

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

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

So it is always advised to use waitForTasksToCompleteOnShutdown and awaitTerminationSeconds properties together. Value of awaitTerminationSeconds depends on our application.

所以总是建议一起使用 waitForTasksToCompleteOnShutdown 和 awaitTerminationSeconds 属性。awaitTerminationSeconds 的值取决于我们的应用程序。