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}