View Javadoc

1   package org.apache.jcs.utils.threadpool;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.util.ArrayList;
23  import java.util.HashMap;
24  import java.util.Properties;
25  import java.util.concurrent.BlockingQueue;
26  import java.util.concurrent.LinkedBlockingQueue;
27  import java.util.concurrent.ThreadFactory;
28  import java.util.concurrent.ThreadPoolExecutor;
29  import java.util.concurrent.TimeUnit;
30  
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.apache.jcs.utils.props.PropertyLoader;
34  import org.apache.jcs.utils.threadpool.PoolConfiguration.WhenBlockedPolicy;
35  
36  /**
37   * This manages threadpools for an application using Doug Lea's Util Concurrent package.
38   * http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html
39   * <p>
40   * It is a singleton since threads need to be managed vm wide.
41   * <p>
42   * This manager forces you to use a bounded queue. By default it uses the current thread for
43   * execution when the buffer is full and no free threads can be created.
44   * <p>
45   * You can specify the props file to use or pass in a properties object prior to configuration. By
46   * default it looks for configuration information in thread_pool.properties.
47   * <p>
48   * If set, the Properties object will take precedence.
49   * <p>
50   * If a value is not set for a particular pool, the hard coded defaults will be used.
51   *
52   * <pre>
53   * int boundarySize_DEFAULT = 2000;
54   *
55   * int maximumPoolSize_DEFAULT = 150;
56   *
57   * int minimumPoolSize_DEFAULT = 4;
58   *
59   * int keepAliveTime_DEFAULT = 1000 * 60 * 5;
60   *
61   * boolean abortWhenBlocked = false;
62   *
63   * String whenBlockedPolicy_DEFAULT = IPoolConfiguration.POLICY_RUN;
64   *
65   * int startUpSize_DEFAULT = 4;
66   * </pre>
67   *
68   * You can configure default settings by specifying a default pool in the properties, ie "cache.ccf"
69   * <p>
70   * @author Aaron Smuts
71   */
72  public class ThreadPoolManager
73  {
74      /** The logger */
75      private static final Log log = LogFactory.getLog( ThreadPoolManager.class );
76  
77      /**
78       * DEFAULT SETTINGS, these are not final since they can be set via the properties file or object
79       */
80      private static boolean useBoundary_DEFAULT = true;
81  
82      /** Default queue size limit */
83      private static int boundarySize_DEFAULT = 2000;
84  
85      /** Default max size */
86      private static int maximumPoolSize_DEFAULT = 150;
87  
88      /** Default min */
89      private static int minimumPoolSize_DEFAULT = 4;
90  
91      /** Default keep alive */
92      private static int keepAliveTime_DEFAULT = 1000 * 60 * 5;
93  
94      /** Default when blocked */
95      private static WhenBlockedPolicy whenBlockedPolicy_DEFAULT = WhenBlockedPolicy.RUN;
96  
97      /** Default startup size */
98      private static int startUpSize_DEFAULT = 4;
99  
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 }