package org.frameworkset.schedule; import java.util.concurrent.ExecutorService; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.apache.log4j.Logger; import org.frameworkset.spi.BeanNameAware; import org.frameworkset.spi.DisposableBean; import org.frameworkset.spi.InitializingBean; public abstract class ExecutorConfigurationSupport extends CustomizableThreadFactory implements BeanNameAware, InitializingBean, DisposableBean { protected final Logger logger = Logger.getLogger(getClass()); private ThreadFactory threadFactory = this; private boolean threadNamePrefixSet = false; private RejectedExecutionHandler rejectedExecutionHandler = new ThreadPoolExecutor.AbortPolicy(); private boolean waitForTasksToCompleteOnShutdown = false; private int awaitTerminationSeconds = 0; private String beanName; private ExecutorService executor; /** * Set the ThreadFactory to use for the ExecutorService's thread pool. * Default is the underlying ExecutorService's default thread factory. * <p> * In a Java EE 7 or other managed environment with JSR-236 support, * consider specifying a JNDI-located ManagedThreadFactory: by default, to * be found at "java:comp/DefaultManagedThreadFactory". Use the * "jee:jndi-lookup" namespace element in XML or the programmatic * {@link org.frameworkset.jndi.JndiLocatorDelegate} for convenient * lookup. Alternatively, consider using Spring's * {@link DefaultManagedAwareThreadFactory} with its fallback to local * threads in case of no managed thread factory found. * * @see java.util.concurrent.Executors#defaultThreadFactory() * @see javax.enterprise.concurrent.ManagedThreadFactory * @see DefaultManagedAwareThreadFactory */ public void setThreadFactory(ThreadFactory threadFactory) { this.threadFactory = (threadFactory != null ? threadFactory : this); } @Override public void setThreadNamePrefix(String threadNamePrefix) { super.setThreadNamePrefix(threadNamePrefix); this.threadNamePrefixSet = true; } /** * Set the RejectedExecutionHandler to use for the ExecutorService. Default * is the ExecutorService's default abort policy. * * @see java.util.concurrent.ThreadPoolExecutor.AbortPolicy */ public void setRejectedExecutionHandler(RejectedExecutionHandler rejectedExecutionHandler) { this.rejectedExecutionHandler = (rejectedExecutionHandler != null ? rejectedExecutionHandler : new ThreadPoolExecutor.AbortPolicy()); } /** * Set whether to wait for scheduled tasks to complete on shutdown, not * interrupting running tasks and executing all tasks in the queue. * <p> * Default is "false", shutting down immediately through interrupting * ongoing tasks and clearing the queue. Switch this flag to "true" if you * prefer fully completed tasks at the expense of a longer shutdown phase. * <p> * Note that 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 {@link #setAwaitTerminationSeconds "awaitTerminationSeconds"} * property instead of or in addition to this property. * * @see java.util.concurrent.ExecutorService#shutdown() * @see java.util.concurrent.ExecutorService#shutdownNow() */ public void setWaitForTasksToCompleteOnShutdown(boolean waitForJobsToCompleteOnShutdown) { this.waitForTasksToCompleteOnShutdown = waitForJobsToCompleteOnShutdown; } /** * Set the maximum number of seconds that this executor is supposed to block * on shutdown in order to wait for remaining tasks to complete their * execution before the rest of the container continues to shut down. This * is particularly useful if your remaining tasks are likely to need access * to other resources that are also managed by the container. * <p> * By default, this executor won't wait for the termination of tasks at all. * It will either shut down immediately, interrupting ongoing tasks and * clearing the remaining task queue - or, if the * {@link #setWaitForTasksToCompleteOnShutdown * "waitForTasksToCompleteOnShutdown"} flag has been set to {@code true}, it * will continue to fully execute all ongoing tasks as well as all remaining * tasks in the queue, in parallel to the rest of the container shutting * down. * <p> * In either case, if you specify an await-termination period using this * property, this executor will wait for the given time (max) for the * termination of tasks. As a rule of thumb, specify a significantly higher * timeout here if you set "waitForTasksToCompleteOnShutdown" to * {@code true} at the same time, since all remaining tasks in the queue * will still get executed - in contrast to the default shutdown behavior * where it's just about waiting for currently executing tasks that aren't * reacting to thread interruption. * * @see java.util.concurrent.ExecutorService#shutdown() * @see java.util.concurrent.ExecutorService#awaitTermination */ public void setAwaitTerminationSeconds(int awaitTerminationSeconds) { this.awaitTerminationSeconds = awaitTerminationSeconds; } @Override public void setBeanName(String name) { this.beanName = name; } /** * Calls {@code initialize()} after the container applied all property * values. * * @see #initialize() */ @Override public void afterPropertiesSet() { initialize(); } /** * Set up the ExecutorService. */ public void initialize() { if (logger.isInfoEnabled()) { logger.info("Initializing ExecutorService " + (this.beanName != null ? " '" + this.beanName + "'" : "")); } if (!this.threadNamePrefixSet && this.beanName != null) { setThreadNamePrefix(this.beanName + "-"); } this.executor = initializeExecutor(this.threadFactory, this.rejectedExecutionHandler); } /** * Create the target {@link java.util.concurrent.ExecutorService} instance. * Called by {@code afterPropertiesSet}. * * @param threadFactory * the ThreadFactory to use * @param rejectedExecutionHandler * the RejectedExecutionHandler to use * @return a new ExecutorService instance * @see #afterPropertiesSet() */ protected abstract ExecutorService initializeExecutor(ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler); /** * Calls {@code shutdown} when the BeanFactory destroys the task executor * instance. * * @see #shutdown() */ @Override public void destroy() { shutdown(); } /** * Perform a shutdown on the underlying ExecutorService. * * @see java.util.concurrent.ExecutorService#shutdown() * @see java.util.concurrent.ExecutorService#shutdownNow() * @see #awaitTerminationIfNecessary() */ public void shutdown() { if (logger.isInfoEnabled()) { logger.info("Shutting down ExecutorService" + (this.beanName != null ? " '" + this.beanName + "'" : "")); } if (this.waitForTasksToCompleteOnShutdown) { this.executor.shutdown(); } else { this.executor.shutdownNow(); } awaitTerminationIfNecessary(); } /** * Wait for the executor to terminate, according to the value of the * {@link #setAwaitTerminationSeconds "awaitTerminationSeconds"} property. */ private void awaitTerminationIfNecessary() { if (this.awaitTerminationSeconds > 0) { try { if (!this.executor.awaitTermination(this.awaitTerminationSeconds, TimeUnit.SECONDS)) { logger.warn("Timed out while waiting for executor" + (this.beanName != null ? " '" + this.beanName + "'" : "") + " to terminate"); } } catch (InterruptedException ex) { logger.warn("Interrupted while waiting for executor" + (this.beanName != null ? " '" + this.beanName + "'" : "") + " to terminate"); Thread.currentThread().interrupt(); } } } }