001/*
002 * Copyright 2002-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.scheduling.concurrent;
018
019import java.util.concurrent.ExecutorService;
020import java.util.concurrent.RejectedExecutionHandler;
021import java.util.concurrent.ThreadFactory;
022import java.util.concurrent.ThreadPoolExecutor;
023import java.util.concurrent.TimeUnit;
024
025import org.apache.commons.logging.Log;
026import org.apache.commons.logging.LogFactory;
027
028import org.springframework.beans.factory.BeanNameAware;
029import org.springframework.beans.factory.DisposableBean;
030import org.springframework.beans.factory.InitializingBean;
031
032/**
033 * Base class for setting up a {@link java.util.concurrent.ExecutorService}
034 * (typically a {@link java.util.concurrent.ThreadPoolExecutor} or
035 * {@link java.util.concurrent.ScheduledThreadPoolExecutor}).
036 * Defines common configuration settings and common lifecycle handling.
037 *
038 * @author Juergen Hoeller
039 * @since 3.0
040 * @see java.util.concurrent.ExecutorService
041 * @see java.util.concurrent.Executors
042 * @see java.util.concurrent.ThreadPoolExecutor
043 * @see java.util.concurrent.ScheduledThreadPoolExecutor
044 */
045@SuppressWarnings("serial")
046public abstract class ExecutorConfigurationSupport extends CustomizableThreadFactory
047                implements BeanNameAware, InitializingBean, DisposableBean {
048
049        protected final Log logger = LogFactory.getLog(getClass());
050
051        private ThreadFactory threadFactory = this;
052
053        private boolean threadNamePrefixSet = false;
054
055        private RejectedExecutionHandler rejectedExecutionHandler = new ThreadPoolExecutor.AbortPolicy();
056
057        private boolean waitForTasksToCompleteOnShutdown = false;
058
059        private int awaitTerminationSeconds = 0;
060
061        private String beanName;
062
063        private ExecutorService executor;
064
065
066        /**
067         * Set the ThreadFactory to use for the ExecutorService's thread pool.
068         * Default is the underlying ExecutorService's default thread factory.
069         * <p>In a Java EE 7 or other managed environment with JSR-236 support,
070         * consider specifying a JNDI-located ManagedThreadFactory: by default,
071         * to be found at "java:comp/DefaultManagedThreadFactory".
072         * Use the "jee:jndi-lookup" namespace element in XML or the programmatic
073         * {@link org.springframework.jndi.JndiLocatorDelegate} for convenient lookup.
074         * Alternatively, consider using Spring's {@link DefaultManagedAwareThreadFactory}
075         * with its fallback to local threads in case of no managed thread factory found.
076         * @see java.util.concurrent.Executors#defaultThreadFactory()
077         * @see javax.enterprise.concurrent.ManagedThreadFactory
078         * @see DefaultManagedAwareThreadFactory
079         */
080        public void setThreadFactory(ThreadFactory threadFactory) {
081                this.threadFactory = (threadFactory != null ? threadFactory : this);
082        }
083
084        @Override
085        public void setThreadNamePrefix(String threadNamePrefix) {
086                super.setThreadNamePrefix(threadNamePrefix);
087                this.threadNamePrefixSet = true;
088        }
089
090        /**
091         * Set the RejectedExecutionHandler to use for the ExecutorService.
092         * Default is the ExecutorService's default abort policy.
093         * @see java.util.concurrent.ThreadPoolExecutor.AbortPolicy
094         */
095        public void setRejectedExecutionHandler(RejectedExecutionHandler rejectedExecutionHandler) {
096                this.rejectedExecutionHandler =
097                                (rejectedExecutionHandler != null ? rejectedExecutionHandler : new ThreadPoolExecutor.AbortPolicy());
098        }
099
100        /**
101         * Set whether to wait for scheduled tasks to complete on shutdown,
102         * not interrupting running tasks and executing all tasks in the queue.
103         * <p>Default is "false", shutting down immediately through interrupting
104         * ongoing tasks and clearing the queue. Switch this flag to "true" if you
105         * prefer fully completed tasks at the expense of a longer shutdown phase.
106         * <p>Note that Spring's container shutdown continues while ongoing tasks
107         * are being completed. If you want this executor to block and wait for the
108         * termination of tasks before the rest of the container continues to shut
109         * down - e.g. in order to keep up other resources that your tasks may need -,
110         * set the {@link #setAwaitTerminationSeconds "awaitTerminationSeconds"}
111         * property instead of or in addition to this property.
112         * @see java.util.concurrent.ExecutorService#shutdown()
113         * @see java.util.concurrent.ExecutorService#shutdownNow()
114         */
115        public void setWaitForTasksToCompleteOnShutdown(boolean waitForJobsToCompleteOnShutdown) {
116                this.waitForTasksToCompleteOnShutdown = waitForJobsToCompleteOnShutdown;
117        }
118
119        /**
120         * Set the maximum number of seconds that this executor is supposed to block
121         * on shutdown in order to wait for remaining tasks to complete their execution
122         * before the rest of the container continues to shut down. This is particularly
123         * useful if your remaining tasks are likely to need access to other resources
124         * that are also managed by the container.
125         * <p>By default, this executor won't wait for the termination of tasks at all.
126         * It will either shut down immediately, interrupting ongoing tasks and clearing
127         * the remaining task queue - or, if the
128         * {@link #setWaitForTasksToCompleteOnShutdown "waitForTasksToCompleteOnShutdown"}
129         * flag has been set to {@code true}, it will continue to fully execute all
130         * ongoing tasks as well as all remaining tasks in the queue, in parallel to
131         * the rest of the container shutting down.
132         * <p>In either case, if you specify an await-termination period using this property,
133         * this executor will wait for the given time (max) for the termination of tasks.
134         * As a rule of thumb, specify a significantly higher timeout here if you set
135         * "waitForTasksToCompleteOnShutdown" to {@code true} at the same time,
136         * since all remaining tasks in the queue will still get executed - in contrast
137         * to the default shutdown behavior where it's just about waiting for currently
138         * executing tasks that aren't reacting to thread interruption.
139         * @see java.util.concurrent.ExecutorService#shutdown()
140         * @see java.util.concurrent.ExecutorService#awaitTermination
141         */
142        public void setAwaitTerminationSeconds(int awaitTerminationSeconds) {
143                this.awaitTerminationSeconds = awaitTerminationSeconds;
144        }
145
146        @Override
147        public void setBeanName(String name) {
148                this.beanName = name;
149        }
150
151
152        /**
153         * Calls {@code initialize()} after the container applied all property values.
154         * @see #initialize()
155         */
156        @Override
157        public void afterPropertiesSet() {
158                initialize();
159        }
160
161        /**
162         * Set up the ExecutorService.
163         */
164        public void initialize() {
165                if (logger.isInfoEnabled()) {
166                        logger.info("Initializing ExecutorService" + (this.beanName != null ? " '" + this.beanName + "'" : ""));
167                }
168                if (!this.threadNamePrefixSet && this.beanName != null) {
169                        setThreadNamePrefix(this.beanName + "-");
170                }
171                this.executor = initializeExecutor(this.threadFactory, this.rejectedExecutionHandler);
172        }
173
174        /**
175         * Create the target {@link java.util.concurrent.ExecutorService} instance.
176         * Called by {@code afterPropertiesSet}.
177         * @param threadFactory the ThreadFactory to use
178         * @param rejectedExecutionHandler the RejectedExecutionHandler to use
179         * @return a new ExecutorService instance
180         * @see #afterPropertiesSet()
181         */
182        protected abstract ExecutorService initializeExecutor(
183                        ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler);
184
185
186        /**
187         * Calls {@code shutdown} when the BeanFactory destroys
188         * the task executor instance.
189         * @see #shutdown()
190         */
191        @Override
192        public void destroy() {
193                shutdown();
194        }
195
196        /**
197         * Perform a shutdown on the underlying ExecutorService.
198         * @see java.util.concurrent.ExecutorService#shutdown()
199         * @see java.util.concurrent.ExecutorService#shutdownNow()
200         */
201        public void shutdown() {
202                if (logger.isInfoEnabled()) {
203                        logger.info("Shutting down ExecutorService" + (this.beanName != null ? " '" + this.beanName + "'" : ""));
204                }
205                if (this.executor != null) {
206                        if (this.waitForTasksToCompleteOnShutdown) {
207                                this.executor.shutdown();
208                        }
209                        else {
210                                this.executor.shutdownNow();
211                        }
212                        awaitTerminationIfNecessary(this.executor);
213                }
214        }
215
216        /**
217         * Wait for the executor to terminate, according to the value of the
218         * {@link #setAwaitTerminationSeconds "awaitTerminationSeconds"} property.
219         */
220        private void awaitTerminationIfNecessary(ExecutorService executor) {
221                if (this.awaitTerminationSeconds > 0) {
222                        try {
223                                if (!executor.awaitTermination(this.awaitTerminationSeconds, TimeUnit.SECONDS)) {
224                                        if (logger.isWarnEnabled()) {
225                                                logger.warn("Timed out while waiting for executor" +
226                                                                (this.beanName != null ? " '" + this.beanName + "'" : "") + " to terminate");
227                                        }
228                                }
229                        }
230                        catch (InterruptedException ex) {
231                                if (logger.isWarnEnabled()) {
232                                        logger.warn("Interrupted while waiting for executor" +
233                                                        (this.beanName != null ? " '" + this.beanName + "'" : "") + " to terminate");
234                                }
235                                Thread.currentThread().interrupt();
236                        }
237                }
238        }
239
240}