1/*2 * Copyright 2000-2001,2004 The Apache Software Foundation.3 * 4 * Licensed under the Apache License, Version 2.0 (the "License");5 * you may not use this file except in compliance with the License.6 * You may obtain a copy of the License at7 * 8 * http://www.apache.org/licenses/LICENSE-2.09 * 10 * Unless required by applicable law or agreed to in writing, software11 * distributed under the License is distributed on an "AS IS" BASIS,12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.13 * See the License for the specific language governing permissions and14 * limitations under the License.15 */1617packageorg.apache.jetspeed.services.threadpool;
1819// Java Stuff20import java.util.*;
21import javax.servlet.ServletConfig;
2223// Turbine Stuff24import org.apache.turbine.services.TurbineBaseService;
2526// Jetspeed classes27import org.apache.jetspeed.services.logging.JetspeedLogFactoryService;
28import org.apache.jetspeed.services.logging.JetspeedLogger;
2930/***31 * This is a Service that provides a simple threadpool usable by all 32 * thread intensive classes in order to optimize resources utilization 33 * screen:<br>34 *35 * <p>It uses 3 parameters for contolling resource usage:36 * <dl>37 * <dt>init.count</dt>38 * <dd>The number of threads to start at initizaliation</dd>39 * <dt>max.count</dt>40 * <dd>The maximum number of threads started by this service</dd>41 * <dt>minspare.count</dt>42 * <dd>The pool tries to keep lways this minimum number if threads43 * available</dd>44 * </dl>45 * </p>46 *47 * @author <a href="mailto:burton@apache.org">Kevin A. Burton</a>48 * @author <a href="mailto:raphael@apache.org">Raphaël Luta</a>49 * @author <a href="mailto:sgala@hisitech.com">Santiago Gala</a>50 * @version $Id: JetspeedThreadPoolService.java,v 1.10 2004/02/23 03:51:31 jford Exp $51 */52publicclassJetspeedThreadPoolService53extends TurbineBaseService
54 implements ThreadPoolService55 {
56/***57 * Static initialization of the logger for this class58 */59protectedstaticfinalJetspeedLogger logger = JetspeedLogFactoryService.getLogger(JetspeedThreadPoolService.class.getName());
6061/***62 * The number of threads to create on initialization63 */64privateint initThreads = 50;
6566/***67 * The maximum number of threads that should ever be created.68 */69privateint maxThreads = 100;
7071/***72 * The minimum amount of threads that should always be available73 */74privateint minSpareThreads = 15;
7576/***77 * The default priority to use when creating new threads.78 */79publicstaticfinalint DEFAULT_THREAD_PRIORITY = Thread.MIN_PRIORITY;
8081/***82 * Stores threads that are available within the pool.83 */84private Vector availableThreads = new Vector();
858687/***88 * The thread group used for all created threads.89 */90private ThreadGroup tg = new ThreadGroup("JetspeedThreadPoolService");
9192/***93 * Create a new queue for adding Runnable objects to.94 */95private Queue queue = new Queue();
9697/***98 * Holds the total number of threads that have ever been processed.99 */100privateint count = 0;
101102103/***104 * Constructor.105 *106 * @exception Exception, a generic exception.107 */108publicJetspeedThreadPoolService()
109 throws Exception
110 {
111 }
112113114/***115 * Late init. Don't return control until early init says we're done.116 */117publicvoid init( )
118 {
119while( !getInit() ) {
120try {
121 Thread.sleep(500);
122 } catch (InterruptedException ie ) {
123 logger.info("ThreadPool service: Waiting for init()..." );
124 }
125 }
126 }
127128/***129 * Called during Turbine.init()130 *131 * @param config A ServletConfig.132 */133publicsynchronizedvoid init( ServletConfig config )
134 {
135if( getInit() ) {
136//Already inited137return;
138 }
139140try141 {
142 logger.info ( "JetspeedThreadPoolService early init()....starting!");
143 initThreadpool(config);
144 setInit(true);
145 logger.info ( "JetspeedThreadPoolService early init()....finished!");
146 }
147catch (Exception e)
148 {
149 logger.error ( "Cannot initialize JetspeedThreadPoolService!", e );
150 }
151152// we don't call setInit(true) yet, because we want init() to be called also153 }
154155/***156 * Processes the Runnable object with an available thread at default priority157 *158 * @see #process( Runnable, int )159 * @param runnable the runnable code to process160 */161publicvoid process( Runnable runnable ) {
162163 process( runnable, Thread.MIN_PRIORITY );
164165 }
166167/***168 * Process a Runnable object by allocating a Thread for it169 * at the given priority170 *171 * @param runnable the runnable code to process172 * @param priority the priority used be the thread that will run this runnable173 */174publicvoid process( Runnable runnable, int priority ) {
175176RunnableThread thread = this.getAvailableThread();
177178if ( thread == null ) {
179180this.getQueue().add( runnable );
181182 } else {
183184try {
185synchronized ( thread ) {
186//get the default priority of this Thread187int defaultPriority = thread.getPriority();
188if( defaultPriority != priority ) {
189//setting priority triggers security checks,190//so we do it only if needed.191 thread.setPriority( priority );
192 }
193 thread.setRunnable( runnable );
194 thread.notify();
195 }
196 } catch ( Throwable t ) {
197 logger.error("Throwable", t);
198 }
199200 }
201202203 }
204205/***206 * Get the number of threads that have been created207 *208 * @return the number of threads currently created by the pool209 */210publicint getThreadCount() {
211returnthis.tg.activeCount();
212 }
213214/***215 * Get the number of threads that are available.216 *217 * @return the number of threads available in the pool218 */219publicint getAvailableThreadCount() {
220returnthis.availableThreads.size();
221 }
222223/***224 * Get the current length of the Runnable queue, waiting for processing225 *226 * @return the length of the queue of waiting processes227 */228publicint getQueueLength() {
229returnthis.getQueue().size();
230 }
231232/***233 * Get the number of threads that have successfully been processed234 * for logging and debugging purposes.235 *236 * @return the number of processes executed since initialization237 */238publicint getThreadProcessedCount() {
239returnthis.count;
240 }
241242/***243 * Get the queue used by the JetspeedThreadPoolService244 *245 * @return the queue holding the waiting processes246 */247 Queue getQueue() {
248returnthis.queue;
249 }
250251/***252 * Place this thread back into the pool so that it can be used again253 *254 * @param thread the thread to release back to the pool255 */256void release( RunnableThread thread ) {
257258synchronized ( this.availableThreads ) {
259260this.availableThreads.addElement( thread );
261262 ++this.count;
263264/*265 It is important to synchronize here because it is possible that266 between the time we check the queue and we get this another267 thread might return and fetch the queue to the end.268 */269synchronized( this.getQueue() ) {
270271//now if there are any objects in the queue add one for processing to 272//the thread that you just freed up.273if ( this.getQueue().size() > 0 ) {
274275 Runnable r = this.getQueue().get();
276277if ( r != null ) {
278this.process( r );
279 } else {
280 logger.info( "JetspeedThreadPoolService: no Runnable found." );
281 }
282283 }
284285 }
286287 }
288289 }
290291/***292 * This method initialized the ThreadPool293 *294 * @param config A ServletConfig.295 */296privatevoid initThreadpool( ServletConfig config )
297 {
298 Properties props = getProperties();
299300try {
301302this.initThreads = Integer.parseInt( props.getProperty( "init.count" ) );
303this.maxThreads = Integer.parseInt( props.getProperty( "max.count" ) );
304this.minSpareThreads = Integer.parseInt( props.getProperty( "minspare.count" ) );
305306 } catch ( NumberFormatException e ) {
307 logger.error("Invalid number format in properties", e);
308 }
309310//create the number of threads needed for initialization311 createThreads( this.initThreads );
312313 }
314315/***316 * Create "count" number of threads and make them available. 317 *318 * @param count the number of threads to create 319 */320privatesynchronizedvoid createThreads( int count ) {
321322//if the amount of threads you are about to create would end up being323//greater than maxThreads then just cap this off to the end point so that324//you end up with exactly maxThreads325if ( this.getThreadCount() < this.maxThreads &&
326this.getThreadCount() + count > this.maxThreads ) {
327328 count = this.maxThreads - this.getThreadCount();
329330 } elseif ( this.getThreadCount() >= this.maxThreads ) {
331332return;
333 }
334335 logger.info( "JetspeedThreadPoolService: creating " +
336 count +
337" more thread(s) for a total of: " +
338 ( this.getThreadCount() + count ) );
339340for (int i = 0; i < count; ++i ) {
341342343//RunnableThread has a static numbering counter344RunnableThread thread = newRunnableThread( this.tg);
345 thread.setPriority( DEFAULT_THREAD_PRIORITY );
346347 thread.start(); //The thread calls release to add...348//SGP this.availableThreads.addElement( thread );349350 }
351352 }
353354/***355 * Get a thread that is available from the pool or null if there are no more 356 * threads left.357 *358 * @return a thread from the pool or null if non available359 */360privateRunnableThread getAvailableThread() {
361362363synchronized( this.availableThreads ) {
364365//if the current number of available threads is less than minSpareThreads366//then we need to create more367368if ( this.getAvailableThreadCount() < this.minSpareThreads ) {
369this.createThreads( this.minSpareThreads );
370 }
371372//now if there aren't any threads available then just return null.373if ( this.getAvailableThreadCount() == 0 ) {
374returnnull;
375 }
376377RunnableThread thread = null;
378379380381//get the element to use382int id = this.availableThreads.size() - 1;
383384385386 thread = (RunnableThread)this.availableThreads.elementAt( id );
387this.availableThreads.removeElementAt( id );
388389return thread;
390 }
391392393 }
394395 }
396397/***398 * Handles holding Runnables until they are ready to be processed. This is an impl399 * of a FIFO (First In First Out) Queue. This makes it possible to add Runnable400 * objects so that they get processed and they pass through the queue in a predictable401 * fashion.402 *403 * @author <a href="mailto:burton@apache.org">Kevin A. Burton</a>404 * @version $Id: JetspeedThreadPoolService.java,v 1.10 2004/02/23 03:51:31 jford Exp $405 */406class Queue {
407408/***409 * Holds Runnables that have been requested to process but there are no 410 * threads available.411 */412private Vector queue = new Vector();
413414/***415 * Add a Runnable object into the queue.416 *417 * @param runnable the process to add to the queue418 */419publicsynchronizedvoid add( Runnable runnable ) {
420 queue.insertElementAt( runnable, 0 );
421 }
422423/***424 * Get a Runnable object from the queue, and then remove it. Return null425 * if no more Runnable objects exist.426 *427 * @return the first Runnable stored in the queue or null if empty428 */429publicsynchronized Runnable get() {
430431if ( this.queue.size() == 0 ) {
432 JetspeedThreadPoolService.logger.info( "JetspeedThreadPoolService->Queue: No more Runnables left in queue. Returning null" );
433returnnull;
434 }
435436int id = queue.size() - 1;
437 Runnable runnable = (Runnable)queue.elementAt( id );
438this.queue.removeElementAt( id );
439440return runnable;
441 }
442443/***444 * Return the size of the queue.445 *446 * @return the size of the queue447 */448publicint size() {
449returnthis.queue.size();
450 }
451452 }