View Javadoc
1   package org.apache.commons.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.Properties;
24  import java.util.concurrent.BlockingQueue;
25  import java.util.concurrent.ConcurrentHashMap;
26  import java.util.concurrent.LinkedBlockingQueue;
27  import java.util.concurrent.ThreadPoolExecutor;
28  import java.util.concurrent.TimeUnit;
29  
30  import org.apache.commons.jcs.utils.threadpool.PoolConfiguration.WhenBlockedPolicy;
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  
34  /**
35   * This manages threadpools for an application
36   * <p>
37   * It is a singleton since threads need to be managed vm wide.
38   * <p>
39   * This manager forces you to use a bounded queue. By default it uses the current thread for
40   * execution when the buffer is full and no free threads can be created.
41   * <p>
42   * You can specify the props file to use or pass in a properties object prior to configuration. By
43   * default it looks for configuration information in thread_pool.properties.
44   * <p>
45   * If set, the Properties object will take precedence.
46   * <p>
47   * If a value is not set for a particular pool, the hard coded defaults will be used.
48   *
49   * <pre>
50   * int boundarySize_DEFAULT = 2000;
51   *
52   * int maximumPoolSize_DEFAULT = 150;
53   *
54   * int minimumPoolSize_DEFAULT = 4;
55   *
56   * int keepAliveTime_DEFAULT = 1000 * 60 * 5;
57   *
58   * boolean abortWhenBlocked = false;
59   *
60   * String whenBlockedPolicy_DEFAULT = IPoolConfiguration.POLICY_RUN;
61   *
62   * int startUpSize_DEFAULT = 4;
63   * </pre>
64   *
65   * You can configure default settings by specifying a default pool in the properties, ie "cache.ccf"
66   * <p>
67   * @author Aaron Smuts
68   */
69  public class ThreadPoolManager
70  {
71      /** The logger */
72      private static final Log log = LogFactory.getLog( ThreadPoolManager.class );
73  
74      /**
75       * DEFAULT SETTINGS, these are not final since they can be set via the properties file or object
76       */
77      private static boolean useBoundary_DEFAULT = true;
78  
79      /** Default queue size limit */
80      private static int boundarySize_DEFAULT = 2000;
81  
82      /** Default max size */
83      private static int maximumPoolSize_DEFAULT = 150;
84  
85      /** Default min */
86      private static int minimumPoolSize_DEFAULT = 4;
87  
88      /** Default keep alive */
89      private static int keepAliveTime_DEFAULT = 1000 * 60 * 5;
90  
91      /** Default when blocked */
92      private static WhenBlockedPolicy whenBlockedPolicy_DEFAULT = WhenBlockedPolicy.RUN;
93  
94      /** Default startup size */
95      private static int startUpSize_DEFAULT = 4;
96  
97      /** The default config, created using property defaults if present, else those above. */
98      private static PoolConfiguration defaultConfig;
99  
100     /** the root property name */
101     private static final String PROP_NAME_ROOT = "thread_pool";
102 
103     /** default property file name */
104     private static final String DEFAULT_PROP_NAME_ROOT = "thread_pool.default";
105 
106     /**
107      * You can specify the properties to be used to configure the thread pool. Setting this post
108      * initialization will have no effect.
109      */
110     private static volatile Properties props = null;
111 
112     /** singleton instance */
113     private static ThreadPoolManager INSTANCE = null;
114 
115     /** Map of names to pools. */
116     private ConcurrentHashMap<String, ThreadPoolExecutor> pools;
117 
118     /**
119      * No instances please. This is a singleton.
120      */
121     private ThreadPoolManager()
122     {
123         this.pools = new ConcurrentHashMap<String, ThreadPoolExecutor>();
124         configure();
125     }
126 
127     /**
128      * Creates a pool based on the configuration info.
129      * <p>
130      * @param config
131      * @return A ThreadPoll wrapper
132      */
133     private ThreadPoolExecutor createPool( PoolConfiguration config )
134     {
135         BlockingQueue<Runnable> queue = null;
136         if ( config.isUseBoundary() )
137         {
138             if ( log.isDebugEnabled() )
139             {
140                 log.debug( "Creating a Bounded Buffer to use for the pool" );
141             }
142 
143             queue = new LinkedBlockingQueue<Runnable>(config.getBoundarySize());
144         }
145         else
146         {
147             if ( log.isDebugEnabled() )
148             {
149                 log.debug( "Creating a non bounded Linked Queue to use for the pool" );
150             }
151             queue = new LinkedBlockingQueue<Runnable>();
152         }
153 
154         ThreadPoolExecutor pool = new ThreadPoolExecutor(
155             config.getStartUpSize(),
156             config.getMaximumPoolSize(),
157             config.getKeepAliveTime(),
158             TimeUnit.MILLISECONDS,
159             queue,
160             new DaemonThreadFactory("JCS-ThreadPoolManager-"));
161 
162         // when blocked policy
163         switch (config.getWhenBlockedPolicy())
164         {
165             case ABORT:
166                 pool.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
167                 break;
168 
169             case RUN:
170                 pool.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
171                 break;
172 
173             case WAIT:
174                 throw new RuntimeException("POLICY_WAIT no longer supported");
175 
176             case DISCARDOLDEST:
177                 pool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
178                 break;
179 
180             default:
181                 break;
182         }
183 
184         pool.prestartAllCoreThreads();
185 
186         return pool;
187     }
188 
189     /**
190      * Returns a configured instance of the ThreadPoolManger To specify a configuration file or
191      * Properties object to use call the appropriate setter prior to calling getInstance.
192      * <p>
193      * @return The single instance of the ThreadPoolManager
194      */
195     public static synchronized ThreadPoolManager getInstance()
196     {
197         if ( INSTANCE == null )
198         {
199             INSTANCE = new ThreadPoolManager();
200         }
201         return INSTANCE;
202     }
203 
204     /**
205      * Dispose of the instance of the ThreadPoolManger and shut down all thread pools
206      */
207     public static synchronized void dispose()
208     {
209         if ( INSTANCE != null )
210         {
211             for ( String poolName : INSTANCE.getPoolNames())
212             {
213                 try
214                 {
215                     INSTANCE.getPool(poolName).shutdownNow();
216                 }
217                 catch (Throwable t)
218                 {
219                     log.warn("Failed to close pool " + poolName, t);
220                 }
221             }
222 
223             INSTANCE = null;
224         }
225     }
226 
227     /**
228      * Returns a pool by name. If a pool by this name does not exist in the configuration file or
229      * properties, one will be created using the default values.
230      * <p>
231      * Pools are lazily created.
232      * <p>
233      * @param name
234      * @return The thread pool configured for the name.
235      */
236     public ThreadPoolExecutor getPool( String name )
237     {
238         ThreadPoolExecutor pool = pools.get( name );
239 
240         if ( pool == null )
241         {
242             if ( log.isDebugEnabled() )
243             {
244                 log.debug( "Creating pool for name [" + name + "]" );
245             }
246 
247             PoolConfiguration config = loadConfig( PROP_NAME_ROOT + "." + name );
248             pool = createPool( config );
249             ThreadPoolExecutor _pool = pools.putIfAbsent( name, pool );
250             if (_pool != null)
251             {
252                 pool = _pool;
253             }
254 
255             if ( log.isDebugEnabled() )
256             {
257                 log.debug( "PoolName = " + getPoolNames() );
258             }
259         }
260 
261         return pool;
262     }
263 
264     /**
265      * Returns the names of all configured pools.
266      * <p>
267      * @return ArrayList of string names
268      */
269     public ArrayList<String> getPoolNames()
270     {
271         return new ArrayList<String>(pools.keySet());
272     }
273 
274     /**
275      * This will be used if it is not null on initialization. Setting this post initialization will
276      * have no effect.
277      * <p>
278      * @param props The props to set.
279      */
280     public static void setProps( Properties props )
281     {
282         ThreadPoolManager.props = props;
283     }
284 
285     /**
286      * Initialize the ThreadPoolManager and create all the pools defined in the configuration.
287      */
288     private static void configure()
289     {
290         if ( log.isDebugEnabled() )
291         {
292             log.debug( "Initializing ThreadPoolManager" );
293         }
294 
295         if ( props == null )
296         {
297             log.warn( "No configuration settings found.  Using hardcoded default values for all pools." );
298             props = new Properties();
299         }
300 
301         // set intial default and then override if new
302         // settings are available
303         defaultConfig = new PoolConfiguration( useBoundary_DEFAULT, boundarySize_DEFAULT, maximumPoolSize_DEFAULT,
304                                                minimumPoolSize_DEFAULT, keepAliveTime_DEFAULT,
305                                                whenBlockedPolicy_DEFAULT, startUpSize_DEFAULT );
306 
307         defaultConfig = loadConfig( DEFAULT_PROP_NAME_ROOT );
308     }
309 
310     /**
311      * Configures the default PoolConfiguration settings.
312      * <p>
313      * @param root
314      * @return PoolConfiguration
315      */
316     private static PoolConfiguration loadConfig( String root )
317     {
318         PoolConfiguration config = defaultConfig.clone();
319 
320         try
321         {
322             config.setUseBoundary( Boolean.parseBoolean( props.getProperty( root + ".useBoundary", "false" ) ) );
323         }
324         catch ( NumberFormatException nfe )
325         {
326             log.error( "useBoundary not a boolean.", nfe );
327         }
328 
329         // load default if they exist
330         try
331         {
332             config.setBoundarySize( Integer.parseInt( props.getProperty( root + ".boundarySize", "2000" ) ) );
333         }
334         catch ( NumberFormatException nfe )
335         {
336             log.error( "boundarySize not a number.", nfe );
337         }
338 
339         // maximum pool size
340         try
341         {
342             config.setMaximumPoolSize( Integer.parseInt( props.getProperty( root + ".maximumPoolSize", "150" ) ) );
343         }
344         catch ( NumberFormatException nfe )
345         {
346             log.error( "maximumPoolSize not a number.", nfe );
347         }
348 
349         // minimum pool size
350         try
351         {
352             config.setMinimumPoolSize( Integer.parseInt( props.getProperty( root + ".minimumPoolSize", "4" ) ) );
353         }
354         catch ( NumberFormatException nfe )
355         {
356             log.error( "minimumPoolSize not a number.", nfe );
357         }
358 
359         // keep alive
360         try
361         {
362             config.setKeepAliveTime( Integer.parseInt( props.getProperty( root + ".keepAliveTime", "300000" ) ) );
363         }
364         catch ( NumberFormatException nfe )
365         {
366             log.error( "keepAliveTime not a number.", nfe );
367         }
368 
369         // when blocked
370         config.setWhenBlockedPolicy( props.getProperty( root + ".whenBlockedPolicy", "RUN" ) );
371 
372         // startupsize
373         try
374         {
375             config.setStartUpSize( Integer.parseInt( props.getProperty( root + ".startUpSize", "4" ) ) );
376         }
377         catch ( NumberFormatException nfe )
378         {
379             log.error( "startUpSize not a number.", nfe );
380         }
381 
382         if ( log.isInfoEnabled() )
383         {
384             log.info( root + " PoolConfiguration = " + config );
385         }
386 
387         return config;
388     }
389 }