// Copyright (c) 2001 Dustin Sallings <dustin@spy.net> package net.spy.concurrent; import java.util.Map; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import net.spy.log.Logger; import net.spy.log.LoggerFactory; /** * A producer/consumer thread pool for easy parallelism. * * <p> * Quick start example (assuming you want to do a million tasks, 15 at time): * * <pre> * // Get a thread pool that will perform 15 tasks at a time * ThreadPool tp=new ThreadPool("Test Pool", 15); * // Start the thread pool * tp.start(); * * // Do the tasks in a loop, throttling to make sure all we don't * // create too many objects that aren't ready to be used. * for(int i=0; i<1000000; i++) { * // Don't have more than 32 unclaimed tasks * tp.waitForTaskCount(32); * tp.addTask(new MyRunnableClass()); * } * tp.waitForCompletion(); * * </pre> * * </p> * * <p> * The ThreadPoolManager instance is responsible for sizing and resizing * the pool. On start(), the ThreadPoolManager will size the pool to the * start size configured in the ThreadPool. The manager will receive a * notification after any task is added to the pool and may choose to take * action at that point. Otherwise, it will sleep for a certain amount of * time (one minute by default) and then begin its main loop verifying the * number of idle threads satisfies the configuration. * </p> * * <p> * If the number of idle threads is too low, it will create as many as is * required to have the appropriate number of idle threads as long as the * total number of threads will not exceed the configured maximum. * </p> * * <p> * If the number of idle threads is too high, it will reduce the total * number of threads by one (for each loop). * </p> * */ public class ThreadPool extends ThreadPoolExecutor { // This is what we monitor for things being checked out (otherwise we // can't tell the difference between adds and check outs). private ThreadPoolObserver monitor=null; // 8,192 should be enough for anybody. private static final int DEFAULT_LIST_LIMIT=8192; // Default number of threads for a thread pool private static final int DEFAULT_NUM_THREADS=5; // Maximum amount of time to wait for an object notification while waiting // for jobs to finish private static final int WAIT_TIMEOUT=5000; // Stores a mapping between a runnable and what thread is executing it so // afterExecute can update the thread. Stupid inconsistent API. private final Map<Runnable, WorkerThread> currentWorkers= new ConcurrentHashMap<Runnable, WorkerThread>(); private transient Logger logger=null; /** * Get an instance of ThreadPool. * * @param name the name of this pool * @param n the core size of this pool * @param max the maximum size of this pool * @param prio the priority of threads created within this pool * @param q the work queue */ public ThreadPool(final String name, int n, int max, int prio, BlockingQueue<Runnable> q) { super(n, max, 1, TimeUnit.SECONDS, q, new MyThreadFactory(name, prio)); setPriority(prio); monitor=new ThreadPoolObserver(); } /** * Get an instance of ThreadPool. * * @param name Name of the pool. * @param n number of threads * @param max the maximum number of threads * @param prio Priority of the child threads. * @param size the queue size (as an ArrayBlockingQueue) */ public ThreadPool(final String name, int n, int max, int prio, int size) { this(name, n, max, prio, new ArrayBlockingQueue<Runnable>(size, true)); } /** * Get an instance of ThreadPool. * * @param name name of the pool * @param n core pool size * @param max max pool size * @param prio priority of threads created within this pool */ public ThreadPool(String name, int n, int max, int prio) { this(name, n, max, prio, DEFAULT_LIST_LIMIT); } /** * Get an instance of ThreadPool. * * @param name name of the pool * @param n core pool size * @param max max pool size */ public ThreadPool(String name, int n, int max) { this(name, n, max, Thread.NORM_PRIORITY); } /** * Get an instance of ThreadPool. * * @param name Name of the pool. * @param n Number of threads. */ public ThreadPool(String name, int n) { this(name, n, n); } /** * Get an instance of ThreadPool with five threads and a normal priority. * * @param name Name of the pool. */ public ThreadPool(String name) { this(name, DEFAULT_NUM_THREADS, Thread.NORM_PRIORITY); } /** * Start the ThreadPool. */ public synchronized void start() { int threads=prestartAllCoreThreads(); getLogger().info("Started %d of %d threads", threads, getCorePoolSize()); } private Logger getLogger() { if(logger == null) { logger=LoggerFactory.getLogger(getClass()); } return(logger); } /** * Find out how many threads are idle. * * This is a snapshot, it is subject to change between the time that * the number is calculated and the time that the value is returned. * * @return the number of threads not currently running a task */ public int getIdleThreadCount() { return(getPoolSize() - getActiveCount()); } /** * String me. */ @Override public String toString() { return(super.toString() + " - " + getQueue().size() + " of a maximum " + DEFAULT_LIST_LIMIT + " tasks queud"); } /** * Get the minimum number of threads that may exist in the thread pool * at any moment. */ public int getMinTotalThreads() { return(getCorePoolSize()); } /** * Get the priority that will be used for any new threads within this * thread group. */ public int getPriority() { MyThreadFactory t=(MyThreadFactory)getThreadFactory(); return(t.priority); } /** * Set the priority to be used for any new threads within this threaad * group. */ public void setPriority(int p) { MyThreadFactory t=(MyThreadFactory)getThreadFactory(); t.setPriority(p); } /** * Get the monitor that receives notifications when a worker thread * finishes a job. */ public ThreadPoolObserver getMonitor() { return(monitor); } /** * Set the observer who will receive notification whenever a task is * completed. */ public void setMonitor(ThreadPoolObserver m) { this.monitor=m; } @Override protected void beforeExecute(Thread t, Runnable r) { super.beforeExecute(t, r); assert t instanceof WorkerThread : "Thread is not a WorkerThread"; WorkerThread wt=(WorkerThread)t; wt.setRunning(r); currentWorkers.put(r, wt); } @Override protected void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); monitor.completedJob(r); WorkerThread wt=currentWorkers.get(r); assert wt != null : "Lost worker for " + r; wt.setRunning(null); currentWorkers.remove(r); } /** * Add a task for one of the threads to execute. * * This method will add a new task to the queue and notify the queue of * the new task (as well as notify the pool manager) then return * immediately. * * @see ThreadPoolRunnable * @exception IndexOutOfBoundsException if the backing list is full */ public void addTask(Runnable r) { execute(r); } /** * Add a task for one of the threads to execute. * * This method will add a new task to the queue, but only wait a * certain amount of time for the task to get picked up. If the task * is not picked up for execution by <i>timeout</i> milliseconds, the * task will not be executed. * * @param r the task to execute * @param timeout the number of milliseconds to wait for it to start * * @return true if the task was started * * @exception IndexOutOfBoundsException if the backing list is full */ public boolean addTask(Runnable r, long timeout) { boolean wasDone=false; Future<?> f=submit(r, true); try { f.get(timeout, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { getLogger().debug("Interrupted while waiting for task", e); f.cancel(true); } catch (ExecutionException e) { getLogger().debug("Task execution threw an exception", e); wasDone=true; } catch (TimeoutException e) { getLogger().debug("Timed out while waiting for execution", e); f.cancel(true); } return wasDone; } /** * Shut down all of the threads after all jobs are complete, and wait * for all tasks to complete. * * <p> * This is a convenience method that calls waitforTaskCount(0), * followed by shutdown(), followed by waitForThreads(). * </p> * * @throws InterruptedException if waitForTaskCount or waitForThreads * throws an exception */ public void waitForCompletion() throws InterruptedException { waitForTaskCount(0); shutdown(); awaitTermination(86400, TimeUnit.SECONDS); } /** * Wait until there are no more than <i>num</i> tasks in the queue. * This is good for throttling task additions. * * @param num the number of tasks for which to wait * @throws InterruptedException if wait fails */ public void waitForTaskCount(int num) throws InterruptedException { synchronized(monitor) { while(getQueue().size() > num) { monitor.wait(WAIT_TIMEOUT); } } } final static class MyThreadFactory implements ThreadFactory { private String name=null; int priority=Thread.NORM_PRIORITY; MyThreadFactory(String nm, int prio) { super(); name=nm; setPriority(prio); } public void setPriority(int to) { if(to<Thread.MIN_PRIORITY || to>Thread.MAX_PRIORITY) { throw new IllegalArgumentException(to + " is an invalid priority."); } } public Thread newThread(Runnable r) { Thread t=new WorkerThread(r, name + " worker"); t.setPriority(priority); return t; } } } // ThreadPool