com.bigdata.util.concurrent.LatchedExecutor Maven / Gradle / Ivy
/**
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
[email protected]
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; version 2 of the License.
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.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.bigdata.util.concurrent;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.Semaphore;
import org.apache.log4j.Logger;
/**
* A fly weight helper class that runs tasks either sequentially or with limited
* parallelism against some thread pool. Deadlock can arise when limited
* parallelism is applied if there are dependencies among the tasks. Limited
* parallelism is enforced using a counting {@link Semaphore}. New tasks can
* start iff the latch is non-zero. The maximum parallelism is the minimum of
* the value specified to the constructor and the potential parallelism of the
* delegate service.
*
* Note: The pattern for running tasks on this service is generally to
* {@link #execute(Runnable)} a {@link Runnable} and to make that
* {@link Runnable} a {@link FutureTask} if you want to await the {@link Future}
* of a {@link Runnable} or {@link Callable} or otherwise manage its execution.
*
* Note: This class can NOT be trivially wrapped as an {@link ExecutorService}
* since the resulting delegation pattern for submit() winds up invoking
* execute() on the delegate {@link ExecutorService} rather than on this class.
*
* @author Bryan Thompson
*
* @todo write unit tests.
*/
public class LatchedExecutor implements Executor {
private static final transient Logger log = Logger
.getLogger(LatchedExecutor.class);
/**
* The delegate executor.
*/
private final Executor executor;
/**
* This is used to limit the concurrency with which tasks submitted to this
* class may execute on the delegate {@link #executor}.
*/
private final Semaphore semaphore;
/**
* A thread-safe blocking queue of pending tasks.
*
* @todo The capacity of this queue does not of necessity need to be
* unbounded.
*/
private final BlockingQueue queue = new LinkedBlockingDeque(/*unbounded*/);
private final int nparallel;
/**
* Return the maximum parallelism allowed by this {@link Executor}.
*/
public int getNParallel() {
return nparallel;
}
public LatchedExecutor(final Executor executor, final int nparallel) {
if (executor == null)
throw new IllegalArgumentException();
if (nparallel < 1)
throw new IllegalArgumentException();
this.executor = executor;
this.nparallel = nparallel;
this.semaphore = new Semaphore(nparallel);
}
@Override
public void execute(final Runnable r) {
if (!queue.offer(new Runnable() {
/*
* Wrap the Runnable in a class that will start the next Runnable
* from the queue when it completes.
*/
@Override
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
})) {
// The queue is full.
throw new RejectedExecutionException();
}
if (semaphore.tryAcquire()) {
// We were able to obtain a permit, so start another task.
scheduleNext();
}
}
/**
* Schedule the next task if one is available (non-blocking).
*
* Pre-condition: The caller has a permit.
*/
private void scheduleNext() {
while (true) {
Runnable next = null;
if ((next = queue.poll()) != null) {
try {
executor.execute(next);
return;
} catch (RejectedExecutionException ex) {
// log error and poll the queue again.
log.error(ex, ex);
continue;
}
} else {
semaphore.release();
return;
}
}
}
}