1 package org.apache.jcs.utils.threadpool;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.Iterator;
25 import java.util.Properties;
26 import java.util.Set;
27
28 import org.apache.commons.logging.Log;
29 import org.apache.commons.logging.LogFactory;
30 import org.apache.jcs.utils.props.PropertyLoader;
31 import org.apache.jcs.utils.threadpool.behavior.IPoolConfiguration;
32
33 import EDU.oswego.cs.dl.util.concurrent.BoundedBuffer;
34 import EDU.oswego.cs.dl.util.concurrent.Channel;
35 import EDU.oswego.cs.dl.util.concurrent.LinkedQueue;
36 import EDU.oswego.cs.dl.util.concurrent.PooledExecutor;
37 import EDU.oswego.cs.dl.util.concurrent.ThreadFactory;
38
39 /***
40 * This manages threadpools for an application using Doug Lea's Util Concurrent
41 * package.
42 * http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html
43 * <p>
44 * It is a singleton since threads need to be managed vm wide.
45 * <p>
46 * This manager forces you to use a bounded queue. By default it uses the
47 * current thread for execuion when the buffer is full and no free threads can
48 * be created.
49 * <p>
50 * You can specify the props file to use or pass in a properties object prior to
51 * configuration. By default it looks for configuration information in
52 * thread_pool.properties.
53 * <p>
54 * If set, the Properties object will take precedence.
55 * <p>
56 * If a value is not set for a particular pool, the hard coded defaults will be
57 * used.
58 *
59 * <pre>
60 * int boundarySize_DEFAULT = 2000;
61 *
62 * int maximumPoolSize_DEFAULT = 150;
63 *
64 * int minimumPoolSize_DEFAULT = 4;
65 *
66 * int keepAliveTime_DEFAULT = 1000 * 60 * 5;
67 *
68 * boolean abortWhenBlocked = false;
69 *
70 * String whenBlockedPolicy_DEFAULT = IPoolConfiguration.POLICY_RUN;
71 *
72 * int startUpSize_DEFAULT = 4;
73 * </pre>
74 *
75 * You can configure default settings by specifying a default pool in the
76 * properties, ie "cache.ccf"
77 * <p>
78 * @author Aaron Smuts
79 */
80 public class ThreadPoolManager
81 {
82 private static final Log log = LogFactory.getLog( ThreadPoolManager.class );
83
84
85
86 private static boolean useBoundary_DEFAULT = true;
87
88 private static int boundarySize_DEFAULT = 2000;
89
90 private static int maximumPoolSize_DEFAULT = 150;
91
92 private static int minimumPoolSize_DEFAULT = 4;
93
94 private static int keepAliveTime_DEFAULT = 1000 * 60 * 5;
95
96 private static String whenBlockedPolicy_DEFAULT = IPoolConfiguration.POLICY_RUN;
97
98 private static int startUpSize_DEFAULT = 4;
99
100 private static PoolConfiguration defaultConfig;
101
102
103
104 private static String propsFileName = "cache.ccf";
105
106
107 private static String PROP_NAME_ROOT = "thread_pool";
108
109 private static String DEFAULT_PROP_NAME_ROOT = "thread_pool.default";
110
111
112
113
114 private static Properties props = null;
115
116 private static HashMap pools = new HashMap();
117
118
119 private static ThreadPoolManager INSTANCE = null;
120
121 /***
122 * No instances please. This is a singleton.
123 */
124 private ThreadPoolManager()
125 {
126 configure();
127 }
128
129 /***
130 * Creates a pool based on the configuration info.
131 * <p>
132 * @param config
133 * @return A ThreadPoll wrapper
134 */
135 private ThreadPool createPool( PoolConfiguration config )
136 {
137 PooledExecutor pool = null;
138 Channel queue = null;
139 if ( config.isUseBoundary() )
140 {
141 if ( log.isDebugEnabled() )
142 {
143 log.debug( "Creating a Bounded Buffer to use for the pool" );
144 }
145 queue = new BoundedBuffer( config.getBoundarySize() );
146 pool = new PooledExecutor( queue, config.getMaximumPoolSize() );
147 pool.setThreadFactory( new MyThreadFactory() );
148 }
149 else
150 {
151 if ( log.isDebugEnabled() )
152 {
153 log.debug( "Creating a non bounded Linked Queue to use for the pool" );
154 }
155 queue = new LinkedQueue();
156 pool = new PooledExecutor( queue, config.getMaximumPoolSize() );
157 }
158
159 pool.setMinimumPoolSize( config.getMinimumPoolSize() );
160 pool.setKeepAliveTime( config.getKeepAliveTime() );
161
162
163 if ( config.getWhenBlockedPolicy().equals( IPoolConfiguration.POLICY_ABORT ) )
164 {
165 pool.abortWhenBlocked();
166 }
167 else if ( config.getWhenBlockedPolicy().equals( IPoolConfiguration.POLICY_RUN ) )
168 {
169 pool.runWhenBlocked();
170 }
171 else if ( config.getWhenBlockedPolicy().equals( IPoolConfiguration.POLICY_WAIT ) )
172 {
173 pool.waitWhenBlocked();
174 }
175 else if ( config.getWhenBlockedPolicy().equals( IPoolConfiguration.POLICY_ABORT ) )
176 {
177 pool.abortWhenBlocked();
178 }
179 else if ( config.getWhenBlockedPolicy().equals( IPoolConfiguration.POLICY_DISCARDOLDEST ) )
180 {
181 pool.discardOldestWhenBlocked();
182 }
183
184 pool.createThreads( config.getStartUpSize() );
185
186 return new ThreadPool( pool, queue );
187 }
188
189 /***
190 * Returns a configured instance of the ThreadPoolManger To specify a
191 * configuation file or Properties object to use call the appropriate setter
192 * prior to calling getInstance.
193 * <p>
194 * @return The single instance of the ThreadPoolManager
195 */
196 public static synchronized ThreadPoolManager getInstance()
197 {
198 if ( INSTANCE == null )
199 {
200 INSTANCE = new ThreadPoolManager();
201 }
202 return INSTANCE;
203 }
204
205 /***
206 * Returns a pool by name. If a pool by this name does not exist in the
207 * configuration file or properties, one will be created using the default
208 * values.
209 * <p>
210 * Pools are lazily created.
211 * <p>
212 * @param name
213 * @return The thread pool configured for the name.
214 */
215 public ThreadPool getPool( String name )
216 {
217 ThreadPool pool = null;
218
219 synchronized ( pools )
220 {
221 pool = (ThreadPool) pools.get( name );
222 if ( pool == null )
223 {
224 if ( log.isDebugEnabled() )
225 {
226 log.debug( "Creating pool for name [" + name + "]" );
227 }
228 PoolConfiguration config = this.loadConfig( PROP_NAME_ROOT + "." + name );
229 pool = createPool( config );
230
231 if ( pool != null )
232 {
233 pools.put( name, pool );
234 }
235
236 if ( log.isDebugEnabled() )
237 {
238 log.debug( "PoolName = " + getPoolNames() );
239 }
240 }
241 }
242
243 return pool;
244 }
245
246 /***
247 * Returns the names of all configured pools.
248 * <p>
249 * @return ArrayList of string names
250 */
251 public ArrayList getPoolNames()
252 {
253 ArrayList poolNames = new ArrayList();
254 synchronized ( pools )
255 {
256 Set names = pools.keySet();
257 Iterator it = names.iterator();
258 while ( it.hasNext() )
259 {
260 poolNames.add( it.next() );
261 }
262 }
263 return poolNames;
264 }
265
266 /***
267 * Setting this post initialization will have no effect.
268 * <p>
269 * @param propsFileName
270 * The propsFileName to set.
271 */
272 public static void setPropsFileName( String propsFileName )
273 {
274 ThreadPoolManager.propsFileName = propsFileName;
275 }
276
277 /***
278 * Returns the name of the properties file that we used to initialize the
279 * pools. If the value was set post-initialization, then it may not be the
280 * file used.
281 * <p>
282 * @return Returns the propsFileName.
283 */
284 public static String getPropsFileName()
285 {
286 return propsFileName;
287 }
288
289 /***
290 * This will be used if it is not null on initialzation. Setting this post
291 * initialization will have no effect.
292 * <p>
293 * @param props
294 * The props to set.
295 */
296 public static void setProps( Properties props )
297 {
298 ThreadPoolManager.props = props;
299 }
300
301 /***
302 * @return Returns the props.
303 */
304 public static Properties getProps()
305 {
306 return props;
307 }
308
309 /***
310 * Intialize the ThreadPoolManager and create all the pools defined in the
311 * configuration.
312 */
313 protected void configure()
314 {
315 if ( log.isDebugEnabled() )
316 {
317 log.debug( "Initializing ThreadPoolManager" );
318 }
319
320 if ( props == null )
321 {
322 try
323 {
324 props = PropertyLoader.loadProperties( propsFileName );
325
326 if ( log.isDebugEnabled() )
327 {
328 log.debug( "File contained " + props.size() + " properties" );
329 }
330 }
331 catch ( Exception e )
332 {
333 log.error( "Problem loading properties. propsFileName [" + propsFileName + "]", e );
334 }
335 }
336
337 if ( props == null )
338 {
339 log.warn( "No configuration settings found. Using hardcoded default values for all pools." );
340 props = new Properties();
341 }
342
343
344
345 defaultConfig = new PoolConfiguration( useBoundary_DEFAULT, boundarySize_DEFAULT, maximumPoolSize_DEFAULT,
346 minimumPoolSize_DEFAULT, keepAliveTime_DEFAULT,
347 whenBlockedPolicy_DEFAULT, startUpSize_DEFAULT );
348
349 defaultConfig = loadConfig( DEFAULT_PROP_NAME_ROOT );
350 }
351
352 /***
353 * Configures the default PoolConfiguration settings.
354 * <p>
355 * @param root
356 * @return PoolConfiguration
357 */
358 protected PoolConfiguration loadConfig( String root )
359 {
360 PoolConfiguration config = (PoolConfiguration) defaultConfig.clone();
361
362 if ( props.containsKey( root + ".useBoundary" ) )
363 {
364 try
365 {
366 config.setUseBoundary( Boolean.valueOf( (String) props.get( root + ".useBoundary" ) ).booleanValue() );
367 }
368 catch ( NumberFormatException nfe )
369 {
370 log.error( "useBoundary not a boolean.", nfe );
371 }
372 }
373
374
375 if ( props.containsKey( root + ".boundarySize" ) )
376 {
377 try
378 {
379 config.setBoundarySize( Integer.parseInt( (String) props.get( root + ".boundarySize" ) ) );
380 }
381 catch ( NumberFormatException nfe )
382 {
383 log.error( "boundarySize not a number.", nfe );
384 }
385 }
386
387
388 if ( props.containsKey( root + ".maximumPoolSize" ) )
389 {
390 try
391 {
392 config.setMaximumPoolSize( Integer.parseInt( (String) props.get( root + ".maximumPoolSize" ) ) );
393 }
394 catch ( NumberFormatException nfe )
395 {
396 log.error( "maximumPoolSize not a number.", nfe );
397 }
398 }
399
400
401 if ( props.containsKey( root + ".minimumPoolSize" ) )
402 {
403 try
404 {
405 config.setMinimumPoolSize( Integer.parseInt( (String) props.get( root + ".minimumPoolSize" ) ) );
406 }
407 catch ( NumberFormatException nfe )
408 {
409 log.error( "minimumPoolSize not a number.", nfe );
410 }
411 }
412
413
414 if ( props.containsKey( root + ".keepAliveTime" ) )
415 {
416 try
417 {
418 config.setKeepAliveTime( Integer.parseInt( (String) props.get( root + ".keepAliveTime" ) ) );
419 }
420 catch ( NumberFormatException nfe )
421 {
422 log.error( "keepAliveTime not a number.", nfe );
423 }
424 }
425
426
427 if ( props.containsKey( root + ".whenBlockedPolicy" ) )
428 {
429 config.setWhenBlockedPolicy( (String) props.get( root + ".whenBlockedPolicy" ) );
430 }
431
432
433 if ( props.containsKey( root + ".startUpSize" ) )
434 {
435 try
436 {
437 config.setStartUpSize( Integer.parseInt( (String) props.get( root + ".startUpSize" ) ) );
438 }
439 catch ( NumberFormatException nfe )
440 {
441 log.error( "startUpSize not a number.", nfe );
442 }
443 }
444
445 if ( log.isInfoEnabled() )
446 {
447 log.info( root + " PoolConfiguration = " + config );
448 }
449
450 return config;
451 }
452
453 /***
454 * Allows us to set the daemon status on the threads.
455 * <p>
456 * @author aaronsm
457 */
458 class MyThreadFactory
459 implements ThreadFactory
460 {
461
462
463
464
465
466 public Thread newThread( Runnable runner )
467 {
468 Thread t = new Thread( runner );
469 t.setDaemon( true );
470 return t;
471 }
472 }
473 }