package org.dcache.util;
import com.google.common.util.concurrent.AbstractListeningExecutorService;
import com.google.common.util.concurrent.Monitor;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
/**
* An executor that places a bound on the number of concurrent tasks.
*
* Differs from Executors#newFixedThreadPool by not holding on to idle
* threads and by sourcing threads from another Executor rather than
* creating them itself.
*
* Combined with Executors#newCachedThreadPool, the achieved semantics is
* that of an unlimited task queue that scales the number of threads to
* a certain limit, while also allowing those threads to time out. These
* semantics cannot be achieved with ThreadPoolExecutor, as that class
* will always create new threads up to the core size, even if allowing
* core threads to time out.
*/
public class BoundedExecutor extends AbstractListeningExecutorService
{
private final Queue<Runnable> workQueue = new ArrayDeque<>();
private final Executor executor;
private final List<Thread> workers;
private int maxThreads;
private int maxQueued;
private int threads;
private final Monitor monitor = new Monitor();
private final Monitor.Guard isTerminated = new Monitor.Guard(monitor) {
@Override
public boolean isSatisfied()
{
return isShutdown && threads == 0;
}
};
private final Worker worker = new Worker();
private boolean isShutdown;
public BoundedExecutor(Executor executor, int maxThreads)
{
this(executor, maxThreads, Integer.MAX_VALUE);
}
public BoundedExecutor(Executor executor, int maxThreads, int maxQueued)
{
this.executor = executor;
this.maxThreads = maxThreads;
this.maxQueued = maxQueued;
this.workers = new ArrayList<>(maxThreads);
}
@Override
public void shutdown()
{
monitor.enter();
try {
isShutdown = true;
} finally {
monitor.leave();
}
}
@Override
public List<Runnable> shutdownNow()
{
monitor.enter();
try {
isShutdown = true;
List<Runnable> unexecutedTasks = new ArrayList<>();
unexecutedTasks.addAll(workQueue);
workQueue.clear();
for (Thread thread : workers) {
thread.interrupt();
}
return unexecutedTasks;
} finally {
monitor.leave();
}
}
@Override
public boolean isShutdown()
{
monitor.enter();
try {
return isShutdown;
} finally {
monitor.leave();
}
}
@Override
public boolean isTerminated()
{
monitor.enter();
try {
return isShutdown && threads == 0;
} finally {
monitor.leave();
}
}
@Override
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException
{
monitor.enter();
try {
return monitor.waitFor(isTerminated, timeout, unit);
} finally {
monitor.leave();
}
}
public void awaitTermination() throws InterruptedException
{
monitor.enter();
try {
monitor.waitFor(isTerminated);
} finally {
monitor.leave();
}
}
public void awaitTerminationUninterruptibly()
{
monitor.enter();
try {
monitor.waitForUninterruptibly(isTerminated);
} finally {
monitor.leave();
}
}
@Override
public void execute(Runnable task)
{
monitor.enter();
try {
if (isShutdown) {
throw new RejectedExecutionException("Executor has been shut down.");
}
if (workQueue.size() - maxThreads + threads >= maxQueued) {
throw new RejectedExecutionException("Executor queue limit has been reached.");
}
if (threads < maxThreads) {
threads++;
executor.execute(worker);
}
workQueue.add(task);
} finally {
monitor.leave();
}
}
public void setMaximumPoolSize(int size)
{
monitor.enter();
try {
maxThreads = size;
int threadsToStart = Math.min(maxThreads - threads, workQueue.size());
for (int i = 0; i < threadsToStart; i++) {
threads++;
executor.execute(worker);
}
} finally {
monitor.leave();
}
}
public int getMaximumPoolSize()
{
monitor.enter();
try {
return maxThreads;
} finally {
monitor.leave();
}
}
public void setMaximumQueueSize(int size)
{
monitor.enter();
try {
maxQueued = size;
} finally {
monitor.leave();
}
}
public int getMaximumQueueSize()
{
monitor.enter();
try {
return maxQueued;
} finally {
monitor.leave();
}
}
private class Worker implements Runnable
{
private Runnable getFirstTask()
{
monitor.enter();
try {
if (workQueue.isEmpty()) {
threads--;
return null;
} else {
workers.add(Thread.currentThread());
return workQueue.remove();
}
} finally {
monitor.leave();
}
}
private Runnable getNextTask()
{
monitor.enter();
try {
if (workQueue.isEmpty() || threads > maxThreads) {
threads--;
workers.remove(Thread.currentThread());
return null;
} else {
return workQueue.remove();
}
} finally {
monitor.leave();
}
}
@Override
public void run()
{
Runnable task = getFirstTask();
while (task != null) {
try {
task.run();
} catch (Throwable t) {
Thread thread = Thread.currentThread();
thread.getUncaughtExceptionHandler().uncaughtException(thread, t);
}
task = getNextTask();
}
}
}
}