// ThreadCache.java
// $Id: ThreadCache.java,v 1.16 2000/08/16 21:37:58 ylafon Exp $
// (c) COPYRIGHT MIT and INRIA, 1996-1997.
// Please first read the full copyright statement in file COPYRIGHT.html
class CachedThread extends Thread {
Runnable runner = null;
boolean alive = true;
ThreadCache cache = null;
CachedThread next = null;
CachedThread prev = null;
boolean terminated = false;
boolean started = false;
boolean firstime = true;
synchronized boolean isTerminated() {
boolean ret = terminated;
terminated = true;
return ret;
}
synchronized Runnable waitForRunner() {
boolean to = false;
while ( alive ) {
// Is a runner available ?
if ( runner != null ) {
Runnable torun = runner;
firstime = false;
runner = null;
return torun;
} else if ( firstime ) {
// This thread will not be declared free until it runs once:
try {
wait();
} catch (InterruptedException ex) {
}
} else if ( alive = cache.isFree(this, to) ) {
// Notify the cache that we are free, and continue if allowed:
try {
int idleto = cache.getIdleTimeout();
to = false;
if ( idleto > 0 ) {
wait(idleto);
to = (runner == null);
} else {
wait();
}
} catch (InterruptedException ex) {
}
}
}
return null;
}
synchronized void kill() {
alive = false;
notify();
}
synchronized boolean wakeup(Runnable runnable) {
if ( alive ) {
runner = runnable;
if ( ! started )
this.start();
notify();
return true;
} else {
return false;
}
}
public synchronized void start() {
super.start();
this.started = true;
}
public void run() {
try {
while ( true ) {
// Wait for a runner:
Runnable torun = waitForRunner();
// If runner, run:
if ( torun != null )
torun.run();
// If dead, stop
if ( ! alive )
break;
}
} finally {
cache.isDead(this);
}
}
CachedThread(ThreadCache cache, int id) {
super(cache.getThreadGroup(), cache.getThreadGroup().getName()+":"+id);
this.cache = cache;
setPriority(cache.getThreadPriority());
setDaemon(true);
}
}
public class ThreadCache {
private static final boolean debug = false;
/**
* Default number of cached threads.
*/
private static final int DEFAULT_CACHESIZE = 5;
/**
* Has this thread cache been initialized ?
*/
protected boolean inited = false;
/**
* The thread group for this thread cache.
*/
protected ThreadGroup group = null;
/**
* Number of cached threads.
*/
protected int cachesize = DEFAULT_CACHESIZE;
/**
* Number of created threads.
*/
protected int threadcount = 0;
/**
* Uniq thread identifier within this ThreadCache instance.
*/
protected int threadid = 0;
/**
* Number of idle threads to always maintain alive.
*/
protected int idlethreads = 0;
/**
* Should we queue thread requests, rather then creating new threads.
*/
protected boolean growasneeded = false;
/**
* Number of used threads
*/
protected int usedthreads = 0;
/**
* List of free threads.
*/
protected CachedThread freelist = null;
protected CachedThread freetail = null;
/**
* The idle timeout, for a thread to wait before being killed.
* Defaults to <strong>5000</strong> milliseconds.
*/
protected int idletimeout = 5000;
/**
* Cached thread priority.
*/
protected int threadpriority = 5;
/**
* Get the idle timeout value for this cache.
* @return The idletimeout value, or negative if no timeout applies.
*/
synchronized final int getIdleTimeout() {
return (threadcount <= idlethreads) ? -1 : idletimeout;
}
/**
* The given thread is about to be declared free.
* @return A boolean, <strong>true</strong> if the thread is to continue
* running, <strong>false</strong> if the thread should stop.
*/
final synchronized boolean isFree(CachedThread t, boolean timedout) {
if ( timedout && (threadcount > idlethreads) ) {
if ( ! t.isTerminated() ) {
threadcount--;
usedthreads--;
notifyAll();
}
return false;
} else if ( threadcount <= cachesize ) {
t.prev = freetail;
if (freetail != null)
freetail.next = t;
freetail = t;
if (freelist == null)
freelist = t;
usedthreads--;
notifyAll();
return true;
} else {
if ( ! t.isTerminated() ) {
threadcount--;
usedthreads--;
notifyAll();
}
return false;
}
}
/**
* The given thread has terminated, cleanup any associated state.
* @param dead The dead CachedThread instance.
*/
final synchronized void isDead(CachedThread t) {
if ( debug )
System.out.println("** "+t+": is dead tc="+threadcount);
if ( ! t.isTerminated() ) {
threadcount--;
notifyAll();
}
}
/**
* Create a new thread within this thread cache.
* @return A new CachedThread instance.
*/
private synchronized CachedThread createThread() {
threadcount++;
threadid++;
return new CachedThread(this, threadid);
}
/**
* Allocate a new thread, as requested.
* @param waitp Should we wait until a thread is available ?
* @return A launched CachedThread instance, or <strong>null</strong> if
* unable to allocate a new thread, and <code>waitp</code> is <strong>
* false</strong>.
*/
protected synchronized CachedThread allocateThread(boolean waitp) {
CachedThread t = null;
while ( true ) {
if ( freelist != null ) {
if ( debug )
System.out.println("*** allocateThread: free thread");
t = freelist;
freelist = freelist.next;
if (freelist != null) {
freelist.prev = null;
} else {
freetail = null;
}
t.next = null;
break;
} else if ((threadcount < cachesize) || growasneeded) {
if ( debug )
System.out.println("*** create new thread.");
t = createThread();
break;
} else if ( waitp ) {
if ( debug )
System.out.println("*** wait for a thread.");
// Wait for a thread to become available
try {
wait();
} catch (InterruptedException ex) {
}
} else {
return null;
}
}
return t;
}
/**
* Set the thread cache size.
* This will also update the number of idle threads to maintain, if
* requested.
* @param cachesize The new thread cache size.
* @param update If <strong>true</strong> also update the number of
* threads to maintain idle.
*/
public synchronized void setCachesize(int cachesize, boolean update) {
this.cachesize = cachesize;
if ( update )
this.idlethreads = (cachesize>>1);
}
/**
* Set the thread cache size.
* Updaet the number of idle threads to keep alive.
* @param cachesize The new thread cache size.
*/
public void setCachesize(int cachesize) {
setCachesize(cachesize, true);
}
/**
* Enable/disable the thread cache to grow as needed.
* This flag should be turned on only if always getting a thread as fast
* as possible is critical.
* @param onoff The toggle.
*/
public void setGrowAsNeeded(boolean onoff) {
this.growasneeded = onoff;
}
/**
* Set all the cached threads priority.
* Changing the cached thread priority should be done before the thread
* cache is initialized, it will <em>not</em> affect already created
* threads.
* @param priority The new cachewd threads priority.
*/
public void setThreadPriority(int priority) {
threadpriority = priority;
}
/**
* Get the cached thread normal priority.
* @return Currently assigned cached thread priority.
*/
public int getThreadPriority() {
return threadpriority;
}
/**
* Set the idle timeout.
* The idle timeout value is used to eliminate threads that have remain
* idle for too long (although the thread cache will ensure that a
* decent minimal number of threads stay around).
* @param idletimeout The new idle timeout.
*/
public synchronized void setIdleTimeout(int idletimeout) {
this.idletimeout = idletimeout;
}
/**
* Request a thread to run on the given object.
* @param runnable The object to run with the allocated thread.
* @param waitp If <strong>true</strong> wait until a free thread is
* available, otherwise, return <strong>false</strong>.
* @return A boolean, <strong>true</strong> if a thread was successfully
* allocated for the given object, <strong>false</strong> otherwise.
*/
public boolean getThread(Runnable runnable, boolean waitp) {
if ( debug )
System.out.println("*** getting a thread for "+runnable);
if ( ! inited )
throw new RuntimeException("Uninitialized thread cache");
// Allocate and launch the thread:
while ( true ) {
CachedThread t = allocateThread(waitp);
if ( t != null ) {
if ( t.wakeup(runnable) ) {
synchronized (this) {
usedthreads++;
}
return true;
}
} else {
return false;
}
}
}
/**
* Get the ThreadGroup managed by this ThreadCache instance.
* @return A ThreadGroup instance.
*/
public ThreadGroup getThreadGroup() {
return group;
}
/**
* Wait until all the threads have finished their duty
*/
public synchronized void waitForCompletion() {
while (usedthreads > 0) {
if ( debug )
System.out.println("*** Waiting for "+usedthreads+ " threads");
try {
wait();
} catch (InterruptedException ex) {
}
}
}
/**
* Initialize the given thread cache.
* This two stage initialize method is done so that configuration
* of the thread cache can be done before any thread get actually
* created.
*/
public synchronized void initialize() {
CachedThread t = createThread();
freelist = t;
freetail = t;
t.next = null;
t.prev = null;
t.start();
for (int i = 1 ; i < idlethreads ; i++) {
t = createThread();
t.next = freelist;
t.prev = null;
freelist.prev = t;
freelist = t;
t.start();
}
inited = true;
}
/**
* Create a thread cache, whose threads are to be children of the group.
* @param group The thread group to which this thread cache is bound.
* @param nstart Number of thread to create in advance.
*/
public ThreadCache(ThreadGroup group) {
this.group = group;
}
/**
* Create a thread cache, after creating a new thread group.
* @param name The name of the thread group to create.
*/
public ThreadCache(String name) {
this(new ThreadGroup(name));
}
/**
* Create a thread cache, after creating a new thread group.
* @param parent The parent of the thread group to create.
* @param name The name of the thread group.
*/
public ThreadCache(ThreadGroup parent, String name) {
this(new ThreadGroup(parent, name));
}
}
|