001 package org.apache.jcs.utils.threadpool;
002
003 /*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements. See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership. The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License. You may obtain a copy of the License at
011 *
012 * http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied. See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022 import java.util.ArrayList;
023 import java.util.HashMap;
024 import java.util.Properties;
025 import java.util.concurrent.BlockingQueue;
026 import java.util.concurrent.LinkedBlockingQueue;
027 import java.util.concurrent.ThreadFactory;
028 import java.util.concurrent.ThreadPoolExecutor;
029 import java.util.concurrent.TimeUnit;
030
031 import org.apache.commons.logging.Log;
032 import org.apache.commons.logging.LogFactory;
033 import org.apache.jcs.utils.props.PropertyLoader;
034 import org.apache.jcs.utils.threadpool.PoolConfiguration.WhenBlockedPolicy;
035
036 /**
037 * This manages threadpools for an application using Doug Lea's Util Concurrent package.
038 * http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html
039 * <p>
040 * It is a singleton since threads need to be managed vm wide.
041 * <p>
042 * This manager forces you to use a bounded queue. By default it uses the current thread for
043 * execution when the buffer is full and no free threads can be created.
044 * <p>
045 * You can specify the props file to use or pass in a properties object prior to configuration. By
046 * default it looks for configuration information in thread_pool.properties.
047 * <p>
048 * If set, the Properties object will take precedence.
049 * <p>
050 * If a value is not set for a particular pool, the hard coded defaults will be used.
051 *
052 * <pre>
053 * int boundarySize_DEFAULT = 2000;
054 *
055 * int maximumPoolSize_DEFAULT = 150;
056 *
057 * int minimumPoolSize_DEFAULT = 4;
058 *
059 * int keepAliveTime_DEFAULT = 1000 * 60 * 5;
060 *
061 * boolean abortWhenBlocked = false;
062 *
063 * String whenBlockedPolicy_DEFAULT = IPoolConfiguration.POLICY_RUN;
064 *
065 * int startUpSize_DEFAULT = 4;
066 * </pre>
067 *
068 * You can configure default settings by specifying a default pool in the properties, ie "cache.ccf"
069 * <p>
070 * @author Aaron Smuts
071 */
072 public class ThreadPoolManager
073 {
074 /** The logger */
075 private static final Log log = LogFactory.getLog( ThreadPoolManager.class );
076
077 /**
078 * DEFAULT SETTINGS, these are not final since they can be set via the properties file or object
079 */
080 private static boolean useBoundary_DEFAULT = true;
081
082 /** Default queue size limit */
083 private static int boundarySize_DEFAULT = 2000;
084
085 /** Default max size */
086 private static int maximumPoolSize_DEFAULT = 150;
087
088 /** Default min */
089 private static int minimumPoolSize_DEFAULT = 4;
090
091 /** Default keep alive */
092 private static int keepAliveTime_DEFAULT = 1000 * 60 * 5;
093
094 /** Default when blocked */
095 private static WhenBlockedPolicy whenBlockedPolicy_DEFAULT = WhenBlockedPolicy.RUN;
096
097 /** Default startup size */
098 private static int startUpSize_DEFAULT = 4;
099
100 /** The default config, created using property defaults if present, else those above. */
101 private static PoolConfiguration defaultConfig;
102
103 /** This is the default value. */
104 public static final String DEFAULT_PROPS_FILE_NAME = "cache.ccf";
105
106 /** Setting this after initialization will have no effect. */
107 private static String propsFileName = null;
108
109 /** the root property name */
110 private static String PROP_NAME_ROOT = "thread_pool";
111
112 /** default property file name */
113 private static String DEFAULT_PROP_NAME_ROOT = "thread_pool.default";
114
115 /**
116 * You can specify the properties to be used to configure the thread pool. Setting this post
117 * initialization will have no effect.
118 */
119 private static volatile Properties props = null;
120
121 /** Map of names to pools. */
122 private static HashMap<String, ThreadPoolExecutor> pools = new HashMap<String, ThreadPoolExecutor>();
123
124 /** singleton instance */
125 private static ThreadPoolManager INSTANCE = null;
126
127 /**
128 * No instances please. This is a singleton.
129 */
130 private ThreadPoolManager()
131 {
132 configure();
133 }
134
135 /**
136 * Creates a pool based on the configuration info.
137 * <p>
138 * @param config
139 * @return A ThreadPoll wrapper
140 */
141 private ThreadPoolExecutor createPool( PoolConfiguration config )
142 {
143 ThreadPoolExecutor pool = null;
144 BlockingQueue<Runnable> queue = null;
145 if ( config.isUseBoundary() )
146 {
147 if ( log.isDebugEnabled() )
148 {
149 log.debug( "Creating a Bounded Buffer to use for the pool" );
150 }
151
152 queue = new LinkedBlockingQueue<Runnable>(config.getBoundarySize());
153 }
154 else
155 {
156 if ( log.isDebugEnabled() )
157 {
158 log.debug( "Creating a non bounded Linked Queue to use for the pool" );
159 }
160 queue = new LinkedBlockingQueue<Runnable>();
161 }
162
163 pool = new ThreadPoolExecutor(config.getStartUpSize(), config.getMaximumPoolSize(),
164 config.getKeepAliveTime(), TimeUnit.MILLISECONDS,
165 queue, new MyThreadFactory());
166
167 // when blocked policy
168 switch (config.getWhenBlockedPolicy())
169 {
170 case ABORT:
171 pool.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
172 break;
173
174 case RUN:
175 pool.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
176 break;
177
178 case WAIT:
179 throw new RuntimeException("POLICY_WAIT no longer supported");
180
181 case DISCARDOLDEST:
182 pool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
183 break;
184
185 default:
186 break;
187 }
188
189 pool.prestartAllCoreThreads();
190
191 return pool;
192 }
193
194 /**
195 * Returns a configured instance of the ThreadPoolManger To specify a configuration file or
196 * Properties object to use call the appropriate setter prior to calling getInstance.
197 * <p>
198 * @return The single instance of the ThreadPoolManager
199 */
200 public static synchronized ThreadPoolManager getInstance()
201 {
202 if ( INSTANCE == null )
203 {
204 INSTANCE = new ThreadPoolManager();
205 }
206 return INSTANCE;
207 }
208
209 /**
210 * Returns a pool by name. If a pool by this name does not exist in the configuration file or
211 * properties, one will be created using the default values.
212 * <p>
213 * Pools are lazily created.
214 * <p>
215 * @param name
216 * @return The thread pool configured for the name.
217 */
218 public ThreadPoolExecutor getPool( String name )
219 {
220 ThreadPoolExecutor pool = null;
221
222 synchronized ( pools )
223 {
224 pool = pools.get( name );
225 if ( pool == null )
226 {
227 if ( log.isDebugEnabled() )
228 {
229 log.debug( "Creating pool for name [" + name + "]" );
230 }
231 PoolConfiguration config = this.loadConfig( PROP_NAME_ROOT + "." + name );
232 pool = createPool( config );
233
234 if ( pool != null )
235 {
236 pools.put( name, pool );
237 }
238
239 if ( log.isDebugEnabled() )
240 {
241 log.debug( "PoolName = " + getPoolNames() );
242 }
243 }
244 }
245
246 return pool;
247 }
248
249 /**
250 * Returns the names of all configured pools.
251 * <p>
252 * @return ArrayList of string names
253 */
254 public ArrayList<String> getPoolNames()
255 {
256 ArrayList<String> poolNames = new ArrayList<String>();
257 synchronized ( pools )
258 {
259 poolNames.addAll(pools.keySet());
260 }
261 return poolNames;
262 }
263
264 /**
265 * Setting this post initialization will have no effect.
266 * <p>
267 * @param propsFileName The propsFileName to set.
268 */
269 public static void setPropsFileName( String propsFileName )
270 {
271 ThreadPoolManager.propsFileName = propsFileName;
272 }
273
274 /**
275 * Returns the name of the properties file that we used to initialize the pools. If the value
276 * was set post-initialization, then it may not be the file used.
277 * <p>
278 * @return Returns the propsFileName.
279 */
280 public static String getPropsFileName()
281 {
282 return propsFileName;
283 }
284
285 /**
286 * This will be used if it is not null on initialization. Setting this post initialization will
287 * have no effect.
288 * <p>
289 * @param props The props to set.
290 */
291 public static void setProps( Properties props )
292 {
293 ThreadPoolManager.props = props;
294 }
295
296 /**
297 * @return Returns the props.
298 */
299 public static Properties getProps()
300 {
301 return props;
302 }
303
304 /**
305 * Initialize the ThreadPoolManager and create all the pools defined in the configuration.
306 */
307 protected void configure()
308 {
309 if ( log.isDebugEnabled() )
310 {
311 log.debug( "Initializing ThreadPoolManager" );
312 }
313
314 if ( props == null )
315 {
316 try
317 {
318 props = PropertyLoader.loadProperties( propsFileName );
319
320 if ( log.isDebugEnabled() )
321 {
322 log.debug( "File contained " + props.size() + " properties" );
323 }
324 }
325 catch ( Exception e )
326 {
327 log.error( "Problem loading properties. propsFileName [" + propsFileName + "]", e );
328 }
329 }
330
331 if ( props == null )
332 {
333 log.warn( "No configuration settings found. Using hardcoded default values for all pools." );
334 props = new Properties();
335 }
336
337 // set intial default and then override if new
338 // settings are available
339 defaultConfig = new PoolConfiguration( useBoundary_DEFAULT, boundarySize_DEFAULT, maximumPoolSize_DEFAULT,
340 minimumPoolSize_DEFAULT, keepAliveTime_DEFAULT,
341 whenBlockedPolicy_DEFAULT, startUpSize_DEFAULT );
342
343 defaultConfig = loadConfig( DEFAULT_PROP_NAME_ROOT );
344 }
345
346 /**
347 * Configures the default PoolConfiguration settings.
348 * <p>
349 * @param root
350 * @return PoolConfiguration
351 */
352 protected PoolConfiguration loadConfig( String root )
353 {
354 PoolConfiguration config = (PoolConfiguration) defaultConfig.clone();
355
356 if ( props.containsKey( root + ".useBoundary" ) )
357 {
358 try
359 {
360 config.setUseBoundary( Boolean.valueOf( (String) props.get( root + ".useBoundary" ) ).booleanValue() );
361 }
362 catch ( NumberFormatException nfe )
363 {
364 log.error( "useBoundary not a boolean.", nfe );
365 }
366 }
367
368 // load default if they exist
369 if ( props.containsKey( root + ".boundarySize" ) )
370 {
371 try
372 {
373 config.setBoundarySize( Integer.parseInt( (String) props.get( root + ".boundarySize" ) ) );
374 }
375 catch ( NumberFormatException nfe )
376 {
377 log.error( "boundarySize not a number.", nfe );
378 }
379 }
380
381 // maximum pool size
382 if ( props.containsKey( root + ".maximumPoolSize" ) )
383 {
384 try
385 {
386 config.setMaximumPoolSize( Integer.parseInt( (String) props.get( root + ".maximumPoolSize" ) ) );
387 }
388 catch ( NumberFormatException nfe )
389 {
390 log.error( "maximumPoolSize not a number.", nfe );
391 }
392 }
393
394 // minimum pool size
395 if ( props.containsKey( root + ".minimumPoolSize" ) )
396 {
397 try
398 {
399 config.setMinimumPoolSize( Integer.parseInt( (String) props.get( root + ".minimumPoolSize" ) ) );
400 }
401 catch ( NumberFormatException nfe )
402 {
403 log.error( "minimumPoolSize not a number.", nfe );
404 }
405 }
406
407 // keep alive
408 if ( props.containsKey( root + ".keepAliveTime" ) )
409 {
410 try
411 {
412 config.setKeepAliveTime( Integer.parseInt( (String) props.get( root + ".keepAliveTime" ) ) );
413 }
414 catch ( NumberFormatException nfe )
415 {
416 log.error( "keepAliveTime not a number.", nfe );
417 }
418 }
419
420 // when blocked
421 if ( props.containsKey( root + ".whenBlockedPolicy" ) )
422 {
423 config.setWhenBlockedPolicy( (String) props.get( root + ".whenBlockedPolicy" ) );
424 }
425
426 // startupsize
427 if ( props.containsKey( root + ".startUpSize" ) )
428 {
429 try
430 {
431 config.setStartUpSize( Integer.parseInt( (String) props.get( root + ".startUpSize" ) ) );
432 }
433 catch ( NumberFormatException nfe )
434 {
435 log.error( "startUpSize not a number.", nfe );
436 }
437 }
438
439 if ( log.isInfoEnabled() )
440 {
441 log.info( root + " PoolConfiguration = " + config );
442 }
443
444 return config;
445 }
446
447 /**
448 * Allows us to set the daemon status on the threads.
449 * <p>
450 * @author aaronsm
451 */
452 protected static class MyThreadFactory
453 implements ThreadFactory
454 {
455 /**
456 * Sets the thread to daemon.
457 * <p>
458 * @param runner
459 * @return a daemon thread
460 */
461 public Thread newThread( Runnable runner )
462 {
463 Thread t = new Thread( runner );
464 String oldName = t.getName();
465 t.setName( "JCS-ThreadPoolManager-" + oldName );
466 t.setDaemon( true );
467 return t;
468 }
469 }
470 }