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.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      // DEFAULT SETTINGS, these are not final since they can be set
85      // via the Propeties file or object
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     // This is the default value. Setting this after
103     // inialization will have no effect
104     private static String propsFileName = "cache.ccf";
105 
106     // the root property name
107     private static String PROP_NAME_ROOT = "thread_pool";
108 
109     private static String DEFAULT_PROP_NAME_ROOT = "thread_pool.default";
110 
111     // You can specify the properties to be used to configure
112     // the thread pool. Setting this post initialization will have
113     // no effect.
114     private static Properties props = null;
115 
116     private static HashMap pools = new HashMap();
117 
118     // singleton instance
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         // when blocked policy
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         // set intial default and then override if new
344         // settings are available
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         // load default if they exist
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         // maximum pool size
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         // minimum pool size
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         // keep alive
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         // when blocked
427         if ( props.containsKey( root + ".whenBlockedPolicy" ) )
428         {
429             config.setWhenBlockedPolicy( (String) props.get( root + ".whenBlockedPolicy" ) );
430         }
431 
432         // startupsize
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          * (non-Javadoc)
463          *
464          * @see EDU.oswego.cs.dl.util.concurrent.ThreadFactory#newThread(java.lang.Runnable)
465          */
466         public Thread newThread( Runnable runner )
467         {
468             Thread t = new Thread( runner );
469             t.setDaemon( true );
470             return t;
471         }
472     }
473 }