Spring Webアプリケーションには、いくつかのDAOおよびサービスレイヤーBeanがあります。 1つのサービスレイヤーBeanに@Async/@Scheduledメソッドがアノテーションされています。これらのメソッドは、他の(自動配線された)Beanに依存します。 XMLで2つのスレッドプールを構成しました。
<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にタスクexecutor/scheduler Beanを最後に初期化するように指示する方法はありますか、またはSpringに最初にそれらを破棄するように指示する方法はありますか?
私の理解では、破壊はinitの順序で行われます。したがって、最後に初期化されたBeanが最初に破棄されます。スレッドプールBeanが最初に破棄されると、現在実行中のすべてのタスクが終了し、依存するBeanにアクセスできます。
また、@ Asyncおよび@Scheduledアノテーションを持つサービスBeanを参照するスレッドプールのdependon-on属性を使用しようとしました。それらは決して実行されないようで、コンテキスト初期化エラーは発生しません。注釈付きサービス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()を呼び出した後、これらのスレッドは適切に閉じませんでした。したがって、私にとっての解決策は、@ PreDestroyが呼び出されると、IOが既に開始され、これ以上IOを開始しないでスレッドを終了させることでした。 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のコンテナのシャットダウンは、進行中のタスクの完了中も継続します。このexecutorをブロックして、タスクの終了を待ってから、コンテナの残りの部分がシャットダウンし続ける場合-たとえばタスクに必要な他のリソースを維持します-このプロパティの代わりに、またはこのプロパティに加えて、「awaitTerminationSeconds」プロパティを設定します。 "
したがって、waitForTasksToCompleteOnShutdownプロパティとawaitTerminationSecondsプロパティを一緒に使用することを常にお勧めします。 awaitTerminationSecondsの値は、アプリケーションによって異なります。