View Javadoc
1   /*
2    * Copyright 2002-2013 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.scheduling.concurrent;
18  
19  import java.util.concurrent.ExecutorService;
20  import java.util.concurrent.RejectedExecutionHandler;
21  import java.util.concurrent.ThreadFactory;
22  import java.util.concurrent.ThreadPoolExecutor;
23  import java.util.concurrent.TimeUnit;
24  
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  
28  import org.springframework.beans.factory.BeanNameAware;
29  import org.springframework.beans.factory.DisposableBean;
30  import org.springframework.beans.factory.InitializingBean;
31  
32  /**
33   * Base class for classes that are setting up a
34   * {@code java.util.concurrent.ExecutorService}
35   * (typically a {@link java.util.concurrent.ThreadPoolExecutor}).
36   * Defines common configuration settings and common lifecycle handling.
37   *
38   * @author Juergen Hoeller
39   * @since 3.0
40   * @see java.util.concurrent.ExecutorService
41   * @see java.util.concurrent.Executors
42   * @see java.util.concurrent.ThreadPoolExecutor
43   */
44  @SuppressWarnings("serial")
45  public abstract class ExecutorConfigurationSupport extends CustomizableThreadFactory
46  		implements BeanNameAware, InitializingBean, DisposableBean {
47  
48  	protected final Log logger = LogFactory.getLog(getClass());
49  
50  	private ThreadFactory threadFactory = this;
51  
52  	private boolean threadNamePrefixSet = false;
53  
54  	private RejectedExecutionHandler rejectedExecutionHandler = new ThreadPoolExecutor.AbortPolicy();
55  
56  	private boolean waitForTasksToCompleteOnShutdown = false;
57  
58  	private int awaitTerminationSeconds = 0;
59  
60  	private String beanName;
61  
62  	private ExecutorService executor;
63  
64  
65  	/**
66  	 * Set the ThreadFactory to use for the ExecutorService's thread pool.
67  	 * Default is the underlying ExecutorService's default thread factory.
68  	 * <p>In a Java EE 7 or other managed environment with JSR-236 support,
69  	 * consider specifying a JNDI-located ManagedThreadFactory: by default,
70  	 * to be found at "java:comp/DefaultManagedThreadFactory".
71  	 * Use the "jee:jndi-lookup" namespace element in XML or the programmatic
72  	 * {@link org.springframework.jndi.JndiLocatorDelegate} for convenient lookup.
73  	 * Alternatively, consider using Spring's {@link DefaultManagedAwareThreadFactory}
74  	 * with its fallback to local threads in case of no managed thread factory found.
75  	 * @see java.util.concurrent.Executors#defaultThreadFactory()
76  	 * @see javax.enterprise.concurrent.ManagedThreadFactory
77  	 * @see DefaultManagedAwareThreadFactory
78  	 */
79  	public void setThreadFactory(ThreadFactory threadFactory) {
80  		this.threadFactory = (threadFactory != null ? threadFactory : this);
81  	}
82  
83  	@Override
84  	public void setThreadNamePrefix(String threadNamePrefix) {
85  		super.setThreadNamePrefix(threadNamePrefix);
86  		this.threadNamePrefixSet = true;
87  	}
88  
89  	/**
90  	 * Set the RejectedExecutionHandler to use for the ExecutorService.
91  	 * Default is the ExecutorService's default abort policy.
92  	 * @see java.util.concurrent.ThreadPoolExecutor.AbortPolicy
93  	 */
94  	public void setRejectedExecutionHandler(RejectedExecutionHandler rejectedExecutionHandler) {
95  		this.rejectedExecutionHandler =
96  				(rejectedExecutionHandler != null ? rejectedExecutionHandler : new ThreadPoolExecutor.AbortPolicy());
97  	}
98  
99  	/**
100 	 * Set whether to wait for scheduled tasks to complete on shutdown,
101 	 * not interrupting running tasks and executing all tasks in the queue.
102 	 * <p>Default is "false", shutting down immediately through interrupting
103 	 * ongoing tasks and clearing the queue. Switch this flag to "true" if you
104 	 * prefer fully completed tasks at the expense of a longer shutdown phase.
105 	 * <p>Note that Spring's container shutdown continues while ongoing tasks
106 	 * are being completed. If you want this executor to block and wait for the
107 	 * termination of tasks before the rest of the container continues to shut
108 	 * down - e.g. in order to keep up other resources that your tasks may need -,
109 	 * set the {@link #setAwaitTerminationSeconds "awaitTerminationSeconds"}
110 	 * property instead of or in addition to this property.
111 	 * @see java.util.concurrent.ExecutorService#shutdown()
112 	 * @see java.util.concurrent.ExecutorService#shutdownNow()
113 	 */
114 	public void setWaitForTasksToCompleteOnShutdown(boolean waitForJobsToCompleteOnShutdown) {
115 		this.waitForTasksToCompleteOnShutdown = waitForJobsToCompleteOnShutdown;
116 	}
117 
118 	/**
119 	 * Set the maximum number of seconds that this executor is supposed to block
120 	 * on shutdown in order to wait for remaining tasks to complete their execution
121 	 * before the rest of the container continues to shut down. This is particularly
122 	 * useful if your remaining tasks are likely to need access to other resources
123 	 * that are also managed by the container.
124 	 * <p>By default, this executor won't wait for the termination of tasks at all.
125 	 * It will either shut down immediately, interrupting ongoing tasks and clearing
126 	 * the remaining task queue - or, if the
127 	 * {@link #setWaitForTasksToCompleteOnShutdown "waitForTasksToCompleteOnShutdown"}
128 	 * flag has been set to {@code true}, it will continue to fully execute all
129 	 * ongoing tasks as well as all remaining tasks in the queue, in parallel to
130 	 * the rest of the container shutting down.
131 	 * <p>In either case, if you specify an await-termination period using this property,
132 	 * this executor will wait for the given time (max) for the termination of tasks.
133 	 * As a rule of thumb, specify a significantly higher timeout here if you set
134 	 * "waitForTasksToCompleteOnShutdown" to {@code true} at the same time,
135 	 * since all remaining tasks in the queue will still get executed - in contrast
136 	 * to the default shutdown behavior where it's just about waiting for currently
137 	 * executing tasks that aren't reacting to thread interruption.
138 	 * @see java.util.concurrent.ExecutorService#shutdown()
139 	 * @see java.util.concurrent.ExecutorService#awaitTermination
140 	 */
141 	public void setAwaitTerminationSeconds(int awaitTerminationSeconds) {
142 		this.awaitTerminationSeconds = awaitTerminationSeconds;
143 	}
144 
145 	@Override
146 	public void setBeanName(String name) {
147 		this.beanName = name;
148 	}
149 
150 
151 	/**
152 	 * Calls {@code initialize()} after the container applied all property values.
153 	 * @see #initialize()
154 	 */
155 	@Override
156 	public void afterPropertiesSet() {
157 		initialize();
158 	}
159 
160 	/**
161 	 * Set up the ExecutorService.
162 	 */
163 	public void initialize() {
164 		if (logger.isInfoEnabled()) {
165 			logger.info("Initializing ExecutorService " + (this.beanName != null ? " '" + this.beanName + "'" : ""));
166 		}
167 		if (!this.threadNamePrefixSet && this.beanName != null) {
168 			setThreadNamePrefix(this.beanName + "-");
169 		}
170 		this.executor = initializeExecutor(this.threadFactory, this.rejectedExecutionHandler);
171 	}
172 
173 	/**
174 	 * Create the target {@link java.util.concurrent.ExecutorService} instance.
175 	 * Called by {@code afterPropertiesSet}.
176 	 * @param threadFactory the ThreadFactory to use
177 	 * @param rejectedExecutionHandler the RejectedExecutionHandler to use
178 	 * @return a new ExecutorService instance
179 	 * @see #afterPropertiesSet()
180 	 */
181 	protected abstract ExecutorService initializeExecutor(
182 			ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler);
183 
184 
185 	/**
186 	 * Calls {@code shutdown} when the BeanFactory destroys
187 	 * the task executor instance.
188 	 * @see #shutdown()
189 	 */
190 	@Override
191 	public void destroy() {
192 		shutdown();
193 	}
194 
195 	/**
196 	 * Perform a shutdown on the underlying ExecutorService.
197 	 * @see java.util.concurrent.ExecutorService#shutdown()
198 	 * @see java.util.concurrent.ExecutorService#shutdownNow()
199 	 * @see #awaitTerminationIfNecessary()
200 	 */
201 	public void shutdown() {
202 		if (logger.isInfoEnabled()) {
203 			logger.info("Shutting down ExecutorService" + (this.beanName != null ? " '" + this.beanName + "'" : ""));
204 		}
205 		if (this.waitForTasksToCompleteOnShutdown) {
206 			this.executor.shutdown();
207 		}
208 		else {
209 			this.executor.shutdownNow();
210 		}
211 		awaitTerminationIfNecessary();
212 	}
213 
214 	/**
215 	 * Wait for the executor to terminate, according to the value of the
216 	 * {@link #setAwaitTerminationSeconds "awaitTerminationSeconds"} property.
217 	 */
218 	private void awaitTerminationIfNecessary() {
219 		if (this.awaitTerminationSeconds > 0) {
220 			try {
221 				if (!this.executor.awaitTermination(this.awaitTerminationSeconds, TimeUnit.SECONDS)) {
222 					if (logger.isWarnEnabled()) {
223 						logger.warn("Timed out while waiting for executor" +
224 								(this.beanName != null ? " '" + this.beanName + "'" : "") + " to terminate");
225 					}
226 				}
227 			}
228 			catch (InterruptedException ex) {
229 				if (logger.isWarnEnabled()) {
230 					logger.warn("Interrupted while waiting for executor" +
231 							(this.beanName != null ? " '" + this.beanName + "'" : "") + " to terminate");
232 				}
233 				Thread.currentThread().interrupt();
234 			}
235 		}
236 	}
237 
238 }