Java tutorial
// 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)); } }