/*
Copyright 1996-2008 Ariba, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
$Id: //ariba/platform/util/core/ariba/util/core/ThreadPool.java#7 $
*/
package ariba.util.core;
import ariba.util.log.Log;
import java.util.Collections;
import java.util.LinkedList;
import java.util.Set;
/**
Standard non-extensible thread pool implememtation on top of
queue. Users provide Runnable implementations and call execute.
<p>
The pool is constituted of Threads retrieve from a
ThreadFactory. Normally, the number of threads created cannot
exceed a specified number of threads, and new threads are created
if necessary until the limit is reached.
<p>
Each thread pool is configured with a {@link
RejectedExecutionHandler}. When the user call execute, the
runnable is added to a queue and a thread is notified. If all
threads are busy, then one will be added until the thread size
limit is reached. When this happens, outstanding runnables are
queued up. When the queue reaches its (configurable) capacity, the
RejectedExecutionHandler will be called upon to handle the
situation.
<p>
At present there are 2 handlers:
<ul>
<li>{@link DefaultRejectedExecutionHandler} simply drops the runnable
and throws a {@link RejectedExecutionException}.</li>
<li>{@link ThreadPoolRejectedExecutionHandler} will increaese the
queue size by roughly 10% to accommodate the new runnables while simultaneously
add more threads regardless of the number of threads already created to
reduce the queue size.</li>
</ul>
<p>
Pooled threads remaining idle for longer than the specified time out, will
be terminated. A timeout of 0 will result in no idle threads being terminated.
<p>
Based on the above discussion, we can arrive at the following guidelines:
<ul>
<li>if response time is important, it is better to have a shorter queue size
and more threads, and use ThreadPoolRejectedExecutionHandler as the handler.
</li>
<li>if memory (or CPU) resource is important (so runnables can wait), it is
better to have a larger queue and less threads, and a handler that does not
add threads indefinitely.
</li>
</ul>
@aribaapi ariba
*/
public class ThreadPool implements Executor
{
private final Set/*<Thread>*/ _threads;
private final RunnableQueue _queue;
private final String _name;
/**
specifies how long a thread can remain idle before being terminated.
The units are in millisecond. A value of 0 is logical equivalent to
no time out.
*/
private final long _inactivityTimeOut;
private final ThreadManager _threadManager;
private final int _maxThreads;
private final int _minThreads;
private int _threadId = 0;
private int _availableThreads = 0;
private final RejectedExecutionHandler _handler;
private ThreadPoolMonitor _monitor = null;
/**
Creates a new ThreadPool starting 1 thread and containing
up to 5 threads. Threads die after 30 minutes of inactivity.
@param name the name used for the threads
@param threadManager the ThreadManager used to manage the threads
@aribaapi ariba
*/
public ThreadPool (String name, ThreadManager threadManager)
{
this(name, threadManager, 1, 5, 0, 50, null);
}
/**
Creates a new ThreadPool.
@param name the name used for the threads
@param threadManager the ThreadManager used to manage the threads
@param minThreads the number of threads to start with
@param maxThreads the maximum number of threads managed by the pool
@param timeout the maximum time a thread remains inactive
@param queueCapacity the size of the queue to hold the runnables. Note
that it is possible for the queue to grow beyond this initial capacity.
@param handlerClassName the name of the handling class. If <code>null</code>
{@link DefaultRejectedExecutionHandler} will be used.
@aribaapi ariba
*/
public ThreadPool (String name,
ThreadManager threadManager,
int minThreads,
int maxThreads,
long timeout,
int queueCapacity,
String handlerClassName)
{
this._inactivityTimeOut = timeout;
this._maxThreads = maxThreads;
this._minThreads = minThreads;
this._name = name;
this._threadManager = threadManager;
this._queue = new RunnableQueue(queueCapacity, _name);
this._threads = Collections.synchronizedSet(SetUtil.set(minThreads));
this._handler = (handlerClassName == null) ?
new DefaultRejectedExecutionHandler() :
(RejectedExecutionHandler)ClassUtil.newInstance(
handlerClassName, "ariba.util.core.RejectedExecutionHandler");
Assert.that(this._handler != null,
"Error instantiating %s", handlerClassName);
for (int i = 0; i < minThreads; i++) {
createExecutor();
}
if (Log.threadpool.isDebugEnabled()) {
Log.threadpool.debug(
"ThreadPool %s created with %s threads to start with",
_name,
Constants.getInteger(minThreads));
Log.threadpool.debug(
"ThreadPool %s is associated with a queue of " +
"%s runnables",
_name,
Constants.getInteger(queueCapacity));
Log.threadpool.debug(
"ThreadPool %s is associated with handler %s",
_name,
_handler.getClass().getName());
}
}
/**
Retrieves a thread from the pool and have it execute
the given runnable.
@param command the task to be executed
@exception RejectedExecutionException if the execution is rejected.
@aribaapi ariba
*/
public synchronized void execute (Runnable command)
{
executeWithoutNotify(command, false, true);
notify();
}
/**
This method is provided merely for use by
{@link ThreadPoolRejectedExecutionHandler}. Use with
caution!! Do not make this public method.
*/
synchronized void forceExecuteWithoutNotify (Runnable command)
{
executeWithoutNotify(command, true, false);
}
private void executeWithoutNotify (Runnable command,
boolean force,
boolean shouldMonitor)
{
if (!_queue.insertRunnable(command, force)) {
_handler.handle(command, this);
}
if ((_availableThreads == 0 && getThreadCount() < _maxThreads) ||
(force && getThreadCount() < (_maxThreads * 10))) {
createExecutor();
}
if (shouldMonitor && _monitor != null) {
_monitor.monitor(this);
}
}
/** not synchronized, last one wins */
public void setMonitor (ThreadPoolMonitor monitor)
{
_monitor = monitor;
}
public ThreadPoolMonitor getMonitor ()
{
return _monitor;
}
/**
Returns the size of the ThreadPool
@aribaapi ariba
*/
public int getThreadCount ()
{
return _threads.size();
}
/**
* Returns the number of objects waiting in the queue
* @aribaapi private
*/
public int getQueueSize ()
{
// this method is not synchronized like any of the
// other methods accessing the queue. But we're just
// accessing the size of the list so it's fine
return _queue.getSize();
}
/**
Creates a new PooledExecutor. We don't synchronize on this
method because the caller synchronizes.
*/
private void createExecutor ()
{
Thread t = null;
try {
PooledExecutor executor = new PooledExecutor();
t = executor.getThread();
addThread(t);
}
catch (OutOfMemoryError oome) {
// Cannot create and/or start the thread because of lack of memory or
// native threads. If thread was created, remove it from _threads.
// Command is still in queue and will be run later.
if (t != null && _threads.contains(t)) {
_threads.remove(t);
}
Log.threadpool.warning(9863, _name, Constants.getInteger(getThreadCount()),
SystemUtil.stackTrace(oome));
}
}
private void addThread (Thread t)
{
_threads.add(t);
if (getThreadCount() > _maxThreads) {
Log.threadpool.info(9032,
_name,
t.getName(),
Constants.getInteger(getThreadCount()));
}
t.start();
}
/**
Remove the thread from the set of managed threads. Returns
a boolean indicating if the specified thread is removed. The
specified thread is not removed if either one of the following
is true:
<ul>
<li>it is null</li>
<li>the total number of threads being managed does not
exceed the minimum configured threads and the forceRemoval
boolean is false</li>
</ul>
@param t the specified thread, can be null, in which case the
thread is not removed.
@return <code>true</code> if the thread is removed,
<code>false</code> otherwise.
*/
private boolean removeThread (Thread t)
{
if (t != null) {
synchronized (this) {
if (getThreadCount() > _minThreads) {
_threads.remove(t);
Log.threadpool.info(9033, _name,
t.getName(),
Constants.getInteger(getThreadCount()));
return true;
}
}
}
return false;
}
private synchronized Runnable nextRunnable ()
throws InterruptedException
{
_availableThreads++;
try {
if (_queue.isEmpty()) {
if (_availableThreads > _maxThreads) {
//
return null;
}
this.wait(_inactivityTimeOut);
}
if (_queue.isEmpty()) {
return null;
}
else {
return _queue.nextRunnable();
}
}
finally {
_availableThreads--;
}
}
/**
Non synchronized queue to store the runnables
No need to synchronize are all the accesses are done
within blocks synchronized on the ThreadPool
*/
private static final class RunnableQueue
{
private final LinkedList/*<Runnable>*/ _runnables = new LinkedList();
private final int _initialMaxQueueSize;
private final int _triggerIncrement;
private int _warningTriggerLevel;
private final String _threadPoolName;
private static final int MinimumTriggerLevel = 10;
RunnableQueue (int maxSize, String threadPoolName)
{
this._initialMaxQueueSize = maxSize;
this._warningTriggerLevel = maxSize;
int triggerSize = (maxSize + 5) / 10;
this._triggerIncrement = triggerSize < MinimumTriggerLevel ?
MinimumTriggerLevel : triggerSize;
this._threadPoolName = threadPoolName;
}
boolean insertRunnable (Runnable r, boolean forceInsert)
{
if (getSize() >= _initialMaxQueueSize &&
!forceInsert)
{
return false;
}
if (getSize() == _warningTriggerLevel) {
Log.threadpool.warning(9031, _threadPoolName,
Constants.getInteger(_warningTriggerLevel));
_warningTriggerLevel += _triggerIncrement;
}
return _runnables.add(r);
}
Runnable nextRunnable ()
{
return (Runnable)_runnables.removeFirst();
}
boolean isEmpty ()
{
return _runnables.isEmpty();
}
int getSize ()
{
return _runnables.size();
}
}
private class PooledExecutor implements Runnable
{
// a PooledExecutor can only be attached to one single thread
private Thread _thread;
PooledExecutor ()
{
_thread = new Thread(this,
Fmt.S("PooledExecutor - %s - %s",
_name,
String.valueOf(++_threadId)));
_thread.setDaemon(true);
}
public Thread getThread ()
{
return _thread;
}
//----- Runnable implementation -----
public void run ()
{
try {
while (true) {
Runnable runnable = null;
try {
runnable = nextRunnable();
}
catch (InterruptedException e) {
}
if (runnable != null) {
_threadManager.setupThread();
try {
runnable.run();
}
catch (Throwable t) { // OK
Log.threadpool.error(2867,
_name,
runnable.getClass().getName(),
SystemUtil.stackTrace(t));
}
finally {
_threadManager.cleanupThread();
}
}
else {
// thread has been inactive for too long
// Ask the threadPool if we should deallocate
// this thread.
if (removeThread(_thread)) {
// set to null so that we don't remove
// it again in the finally block.
_thread = null;
break;
}
}
}
}
finally {
removeThread(_thread);
}
}
}
}