Java tutorial
/* Copyright (C) 2005-2008 by Peter Eastman 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. */ import java.util.concurrent.atomic.*; import java.util.*; /** * This class coordinates threads for multi-threaded operations. The execution model * provided by this class is a single "task" (e.g. tracing a ray through a single pixel) * which must be executed many times. The task is parameterized by a single index * (e.g. the column containing the pixel). * <p> * To use this class, pass it an object which implements the Task interface. It * automatically creates an appropriate number of threads based on the number of * available processors. When you call run(), the task is repeatedly executed by * the worker threads, with the index running * over the desired range. You may invoke run() any number of times (e.g. once * for each row of the image). Finally, call finish() to clean up the worker threads. */ public class ThreadManager { private int numIndices; private AtomicInteger nextIndex; private Thread thread[]; private HashSet<Thread> waitingThreads; private Task task; private Object controller; private boolean controllerWaiting; /** * Create a new uninitialized ThreadManager. You must invoke setNumIndices() and setTask() * to initialize it before calling run(). */ public ThreadManager() { this(0, null); } /** * Create a new ThreadManager. * * @param numIndices the number of values the index should take on (from 0 to numIndices-1) * @param task the task to perform */ public ThreadManager(int numIndices, Task task) { this.numIndices = numIndices; this.task = task; nextIndex = new AtomicInteger(numIndices); controller = new Object(); controllerWaiting = false; waitingThreads = new HashSet<Thread>(); } /** * Create and start the worker threads. This is invoked the first time run() is called. */ private void createThreads() { // Create a worker thread for each processor. thread = new Thread[Runtime.getRuntime().availableProcessors()]; for (int i = 0; i < thread.length; i++) { thread[i] = new Thread("Worker thread " + (i + 1)) { public void run() { // Repeatedly perform the task until we are finished. while (true) { try { int index = nextIndex(); task.execute(index); } catch (InterruptedException ex) { task.cleanup(); return; } catch (Exception ex) { cancel(); ex.printStackTrace(); } } } }; thread[i].start(); } } /** * Set the number of values the index should take on. This must be invoked from the same * thread that instantiated the ThreadManager and that calls run(). */ public void setNumIndices(int numIndices) { this.numIndices = numIndices; nextIndex.set(numIndices); } /** * Set the Task to be executed by the worker threads. If another Task has already been set, * that one is discarded immediately and cleanup() will never be invoked on in. This method * must be invoked from the same thread that instantiated the ThreadManager and that calls run(). */ public void setTask(Task task) { this.task = task; } /** * Perform the task the specified number of times. This method blocks until all * occurrences of the task are completed. If the current thread is interrupted * while this method is in progress, all of the worker threads will be interrupted * and disposed of. */ public void run() { controllerWaiting = false; nextIndex.set(0); waitingThreads.clear(); if (thread == null) createThreads(); // Notify all the worker threads, then wait for them to finish. synchronized (this) { notifyAll(); } synchronized (controller) { try { controllerWaiting = true; controller.wait(); } catch (InterruptedException ex) { finish(); } } } /** * Cancel a run which is in progress. Calling this method does not interrupt any tasks that * are currently executing, but it prevents any more from being started until the next time * run() is called. */ public void cancel() { nextIndex.set(numIndices); } /** * Dispose of all the worker threads. Once this has been called, do not call run() again. */ public void finish() { if (thread != null) for (int i = 0; i < thread.length; i++) thread[i].interrupt(); } private int nextIndex() throws InterruptedException { int index; while ((index = nextIndex.getAndIncrement()) >= numIndices) { // Wait until run() is called again. synchronized (this) { waitingThreads.add(Thread.currentThread()); if (waitingThreads.size() == thread.length) { while (!controllerWaiting) wait(1); synchronized (controller) { controller.notify(); } } wait(); } } return index; } /** * This interface defines a task to be performed by the worker threads. */ public static interface Task { /** * Execute the task for the specified index. */ public void execute(int index); /** * This is called once from each worker thread when finish() is called. It gives a chance * to do any necessary cleanup. */ public void cleanup(); } }