package org.oddjob.scheduling;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.AbstractExecutorService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.log4j.Logger;
/**
* An {@link ExecutorService} that limits the number of {@link Runnable}s
* running.
* <p>
* Work will be executed in the order in which it was submitted.
*
* @author rob
*
*/
public class ExecutorServiceThrottle extends AbstractExecutorService {
private static final Logger logger = Logger.getLogger(
ExecutorServiceThrottle.class);
/**
* The original {@link ExecutorService} that will actually do the
* executing.
*/
private final ExecutorService executor;
/**
* Outstanding work submitted to the executor
*/
private final LinkedList<Runnable> work = new LinkedList<Runnable>();
/**
* The throttle limit.
*/
private final int threads;
/**
* The number of currently running {@link Runnable}s.
*/
private final AtomicInteger count = new AtomicInteger();
public ExecutorServiceThrottle(ExecutorService delegate, int threads) {
this.executor = delegate;
this.threads = threads;
}
@Override
public boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException {
throw new UnsupportedOperationException();
}
@Override
public boolean isShutdown() {
return executor.isShutdown();
}
@Override
public boolean isTerminated() {
return executor.isTerminated();
}
@Override
public void shutdown() {
throw new UnsupportedOperationException();
}
@Override
public List<Runnable> shutdownNow() {
throw new UnsupportedOperationException();
}
@Override
public void execute(final Runnable command) {
logger.debug("Queueing [" + command + "]");
synchronized (work) {
work.add(new Runnable() {
@Override
public void run() {
try {
command.run();
logger.debug("Completed [" + command + "]");
}
finally {
count.decrementAndGet();
submit();
}
}
@Override
public String toString() {
return ExecutorServiceThrottle.class.getSimpleName() +
" wrapper for " + command;
}
});
}
submit();
}
/**
* Submit any outstanding work if the number of {@link Runnable}s is less
* than the limit.
*/
private void submit() {
synchronized (work) {
if (work.isEmpty()) {
return;
}
if (executor.isShutdown()) {
work.clear();
return;
}
if (count.get() < threads) {
count.incrementAndGet();
Runnable command = work.removeFirst();
logger.debug("Executing [" + command + "]");
executor.execute(command);
}
}
}
@Override
public String toString() {
return getClass().getSimpleName() + ", limit=" + threads +
" executing=" + count;
}
}