Java tutorial
/* * Runs multiple jobs in parallel * Copyright (C) 2004-2005 Matt Conway * http://simplygenius.com/ * Copyright (C) 2005 Stephen Ostermiller * http://ostermiller.org/contact.pl?regarding=Java+Utilities * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * See COPYING.TXT for details. */ import java.util.*; /** * Runs multiple jobs in parallel, n threads at a time, and waits * until all threads are complete before continuing. * <p> * Typically, Parallelizer would be used to run each of the items- * in a for loop at the same time. For example the following for * loop: * <pre> * for (int i=0; i<10; i++){ * System.out.println("Hello World " + i); * } * System.out.println("done"); * </pre> * To this: * <pre> * Parallelizer parallelizer = new Parallelizer(); * for (int i=0; i<10; i++){ * final int j = i; * parallelizer.run( * new Runnable(){ * System.out.println("Hello World " + j); * } * ); * } * parallelizer.join(); * System.out.println("done"); * * More information about this class is available from <a target="_top" href= * "http://ostermiller.org/utils/Parallelizer.html">ostermiller.org</a>. * * @author Matt Conway - http://simplygenius.com/ * @author Stephen Ostermiller - http://ostermiller.org/contact.pl?regarding=Java+Utilities * @since ostermillerutils 1.05.00 */ public class Parallelizer { /** * Constant that may be passed concurrentThreadLimit argument * of the constructor indicating that no limit should be placed * on the number of threads that are allowed to run concurrently. * * @since ostermillerutils 1.05.00 */ public static final int INFINITE_THREAD_LIMIT = 0; /** * The number of threads that are allowed to be run concurrently. * (INFINITE_THREAD_LIMIT for no limit) */ private int concurrentThreadLimit = INFINITE_THREAD_LIMIT; /** * Create a new Parallelizer with no limit on the number * of threads that will be allowed to be run concurrently. * * @since ostermillerutils 1.05.00 */ public Parallelizer() { this(INFINITE_THREAD_LIMIT); } /** * Create a new Parallelizer with the specified limit on the number * of threads that will be allowed to be run concurrently. * <p> * When the concurrent thread limit is reached and the parallelizer * gets a new thread to run, the new thread will be queued until * a thread finishes. * * @param concurrentThreadLimit number of threads that will be allowed * to run simultaneously or INFINITE_THREAD_LIMIT for no limit. * @throws IllegalArgumentException if concurrentThreadLimit not a whole * number or INFINITE_THREAD_LIMIT * * @since ostermillerutils 1.05.00 */ public Parallelizer(int concurrentThreadLimit) { if (concurrentThreadLimit < INFINITE_THREAD_LIMIT) throw new IllegalArgumentException("Bad concurrent thread limit: " + concurrentThreadLimit); this.concurrentThreadLimit = concurrentThreadLimit; } /** * A Set of threads that are currently running. * This set is also used as a lock to synchronize * anything that touches running threads. */ private HashSet<Thread> runningThreads = new HashSet<Thread>(); /** * A queue of jobs that have not yet been started. */ private LinkedList<Thread> toRunQueue = new LinkedList<Thread>(); /** * Run the given job. The given job is either run * immediately or if the max number of concurrent jobs are already * running, it is queued to be run when some job is finished. * <p> * If this method throws an error, that * error may be handled and this method * may be called again as it will not re-throw the same * instance of the error. * * @param job job which is to be run in parallel with other jobs. * @throws Error if any thread that is already running has thrown an Error. * @throws NullPointerException if job is null. * * @since ostermillerutils 1.05.00 */ public void run(Runnable job) { run(null, job, null, 0); } /** * Run the given job. The given job is either run * immediately or if the max number of concurrent jobs are already * running, it is queued to be run when some job is finished. * <p> * If this method throws an error, that * error may be handled and this method * may be called again as it will not re-throw the same * instance of the error. * * @param job job which is to be run in parallel with other jobs. * @param threadName name for the thread that will be created to run the job (null for auto generated thread name) * @throws Error if any thread that is already running has thrown an Error. * @throws NullPointerException if job is null. * * @since ostermillerutils 1.05.00 */ public void run(Runnable job, String threadName) { run(null, job, threadName, 0); } /** * Run the given job. The given job is either run * immediately or if the max number of concurrent jobs are already * running, it is queued to be run when some job is finished. * <p> * If this method throws an error, that * error may be handled and this method * may be called again as it will not re-throw the same * instance of the error. * * @param threadGroup group in which this job should be run (null for default group). * @param job job which is to be run in parallel with other jobs. * @throws Error if any thread that is already running has thrown an Error. * @throws NullPointerException if job is null. * * @since ostermillerutils 1.05.00 */ public void run(ThreadGroup threadGroup, Runnable job) { run(threadGroup, job, null, 0); } /** * Run the given job. The given job is either run * immediately or if the max number of concurrent jobs are already * running, it is queued to be run when some job is finished. * <p> * If this method throws an error, that * error may be handled and this method * may be called again as it will not re-throw the same * instance of the error. * * @param threadGroup group in which this job should be run (null for default group). * @param job job which is to be run in parallel with other jobs. * @param threadName name for the thread that will be created to run the job (null for auto generated thread name) * @throws Error if any thread that is already running has thrown an Error. * @throws NullPointerException if job is null. * * @since ostermillerutils 1.05.00 */ public void run(ThreadGroup threadGroup, Runnable job, String threadName) { run(threadGroup, job, threadName, 0); } /** * Run the given job. The given job is either run * immediately or if the max number of concurrent jobs are already * running, it is queued to be run when some job is finished. * <p> * If this method throws an error, that * error may be handled and this method * may be called again as it will not re-throw the same * instance of the error. * * @param threadGroup group in which this job should be run (null for default group). * @param job job which is to be run in parallel with other jobs. * @param threadName name for the thread that will be created to run the job (null for auto generated thread name) * @param stackSize system dependent stack size suggestion for thread creation (0 for default stack size). * @throws Error if any thread that is already running has thrown an Error. * @throws NullPointerException if job is null. * * @since ostermillerutils 1.05.00 */ public void run(ThreadGroup threadGroup, final Runnable job, String threadName, long stackSize) { throwFirstError(); Runnable jobWrapper = new Runnable() { public void run() { try { job.run(); } catch (RuntimeException runtimeException) { // Put exceptions in the exception queue synchronized (runningThreads) { exceptionList.add(runtimeException); } } catch (Error error) { // Put errors in the error queue synchronized (runningThreads) { errorList.add(error); } } finally { synchronized (runningThreads) { // when done remove ourselves from the list // of running threads. runningThreads.remove(Thread.currentThread()); // Notify the block method. runningThreads.notifyAll(); } // If there are jobs queued up to be run, now would // be a good time to run them. startAJobIfNeeded(); } } }; // ensure the thread name is not null, and auto generate a name if it is threadName = getNextThreadName(threadName); // If we are already running the max number of jobs, queue this job up synchronized (runningThreads) { toRunQueue.add(new Thread(threadGroup, jobWrapper, threadName, stackSize)); } // Now that the job is in the queue of jobs to run, // check the queue and see if the job should be started startAJobIfNeeded(); } /** * An number to assign to the next auto generated thread name */ private static int threadNameCount = 0; /** * Ensure the given thread name is not null. If not null, return it, * if it is null, then then generate a name. * * @param threadName existing thread name to check * @return the given thread name or a generated thread name if the specified name was null. */ private static String getNextThreadName(String threadName) { if (threadName != null) return threadName; return "Parallelizer-" + (threadNameCount++); } /** * A queue of exceptions that running threads have thrown. */ private LinkedList<RuntimeException> exceptionList = new LinkedList<RuntimeException>(); /** * Remove the first exception from the exception list and throw it. * * @throws RuntimeException if a running thread has thrown an exception not yet thrown by this method. */ private void throwFirstException() { synchronized (runningThreads) { if (exceptionList.size() > 0) { throw exceptionList.removeFirst(); } } } /** * A queue of exceptions that running threads have thrown. */ private LinkedList<Error> errorList = new LinkedList<Error>(); /** * Remove the first error from the error list and throw it. * * @throws Error if a running thread has thrown an error not yet thrown by this method. */ private void throwFirstError() throws Error { synchronized (runningThreads) { if (errorList.size() > 0) { throw errorList.removeFirst(); } } } /** * Remove a job from the toRunQueue, create a thread for it, * start the thread, and put the job in the set of running jobs. * But do all this only if there are jobs queued up to be run * and we are not already running the max number of concurrent * jobs at once. */ private void startAJobIfNeeded() { synchronized (runningThreads) { // If we are already running the max number of jobs, just return if (concurrentThreadLimit != INFINITE_THREAD_LIMIT) { if (runningThreads.size() >= concurrentThreadLimit) return; } // If there are no more job to run, return if (toRunQueue.size() == 0) return; // Get a job out of the queue Thread thread = toRunQueue.removeFirst(); // Put the thread in the list of running threads runningThreads.add(thread); thread.start(); } } /** * Return true iff all jobs that have been requested to run * in this Parallelizer have completed. * <p> * If this method throws an error, that * error may be handled and this method * may be called again as it will not re-throw the same * instance of the error. * * @return Whether all jobs are done or not. * @throws Error if any of the running threads has thrown an Error. * * @since ostermillerutils 1.05.00 */ public boolean done() { throwFirstError(); synchronized (runningThreads) { return (toRunQueue.size() + runningThreads.size()) == 0; } } /** * All currently running threads will be interrupted. * The threads interrupted threads may die, causing * jobs that were queued but not yet started, to start. * <p> * If this method throws an error, that * error may be handled and this method * may be called again as it will not re-throw the same * instance of the error. * * @throws Error if any of the running threads has thrown an Error. * * @since ostermillerutils 1.05.00 */ public void interrupt() { throwFirstError(); synchronized (runningThreads) { for (Thread thread : runningThreads) { (thread).interrupt(); throwFirstError(); } } } /** * Dump the stack of each running thread. * <p> * If this method throws an error, that * error may be handled and this method * may be called again as it will not re-throw the same * instance of the error. * * @throws Error if any of the running threads has thrown an Error. * * @since ostermillerutils 1.05.00 */ public void dumpStack() { throwFirstError(); synchronized (runningThreads) { for (Thread thread : runningThreads) { for (StackTraceElement stackTraceElement : thread.getStackTrace()) { System.out.println(stackTraceElement.toString()); } throwFirstError(); } } } /** * Gets a list of all running threads. There may be jobs that * are queued and do not yet have threads. These job are not * returned. * <p> * If this method throws an error, that * error may be handled and this method * may be called again as it will not re-throw the same * instance of the error. * * @throws Error if any of the running threads has thrown an Error. * @return an array of all currently running threads. * * @since ostermillerutils 1.05.00 */ public Thread[] getRunningThreads() { throwFirstError(); synchronized (runningThreads) { return runningThreads.toArray(new Thread[0]); } } /** * Block until all the jobs in this Parallelizer have run * and then return. * <p> * If this method throws an exception or an error, that * exception or error may be handled and this method * may be called again as it will not re-throw the same * instance of the exception or error. * * @throws InterruptedException if interrupted while waiting. * @throws RuntimeException any running thread throws or has thrown a runtime exception. * @throws Error if any of the running threads throws or has thrown an Error. * * @since ostermillerutils 1.05.00 */ public void join() throws InterruptedException { while (!done()) { synchronized (runningThreads) { throwFirstException(); runningThreads.wait(); throwFirstError(); throwFirstException(); } } } }