package winstone;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.AbstractExecutorService;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Wraps {@link Executor} so that we only ask the wrapped Executor to execute N number of tasks
* at any given time.
*
* <p>
* The intention is to use this with {@link ThreadPoolExecutor} with {@link SynchronousQueue}
* with unbounded max capacity (so that for up to N tasks we keep creating more threads for work,
* but beyond that we start to push the tasks into the queue of an infinite capacity.)
*
* <p>
* This is necessary because {@link ThreadPoolExecutor} tries to push work into the queue
* first and only create more threads once the queue is full, so for a queue with infinite
* capacity it'll never create threads beyond the core pool size.
* See http://www.kimchy.org/juc-executorservice-gotcha/ for more discussion of this.
*
* <p>
* Because there's no call back to tell us when the wrapped {@link ExecutorService} has
* finished executing something, this class needs to hand out the next task slightly
* before the wrapped {@link ExecutorService} is done with the previous task. The net result
* is that the wrapped {@link ExecutorService} will end up running N+1 threads (of which
* 1 is almost always idle.) I'm not sure how to fix this.
*
* @author Kohsuke Kawaguchi
*/
public class BoundedExecutorService extends AbstractExecutorService {
/**
* The FIFO queue of tasks waiting to be handed to the wrapped {@link ExecutorService}.
*/
private final List<Runnable> tasks = new LinkedList<Runnable>();
private final ExecutorService base;
private final int max;
/**
* How many tasks the wrapped {@link ExecutorService} is executing right now?
* Touched only in a synchronized block.
*/
private int current;
private boolean isShutdown = false;
public BoundedExecutorService(ExecutorService base, int max) {
this.base = base;
this.max = max;
}
public synchronized void execute(final Runnable r) {
if (isShutdown)
throw new RejectedExecutionException("already shut down");
tasks.add(r);
if (current < max)
scheduleNext();
}
private synchronized void scheduleNext() {
if (tasks.isEmpty()) {
if (isShutdown)
base.shutdown();
return;
}
final Runnable task = tasks.remove(0);
base.execute(new Runnable() {
public void run() {
try {
task.run();
} finally {
done();
}
}
});
current++;
}
private synchronized void done() {
current--;
scheduleNext(); // we already know that current<max
}
public synchronized void shutdown() {
isShutdown = true;
if (tasks.isEmpty())
base.shutdown();
}
public synchronized List<Runnable> shutdownNow() {
isShutdown = true;
List<Runnable> r = base.shutdownNow();
r.addAll(tasks);
tasks.clear();
return r;
}
public boolean isShutdown() {
return isShutdown;
}
public boolean isTerminated() {
return base.isTerminated();
}
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
return base.awaitTermination(timeout,unit);
}
}