/** * */ package vroom.common.utilities; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * <code>BatchThreadPoolExecutor</code> is a specialization of {@link ThreadPoolExecutor} designed to maintain a pool of * {@link Thread} that will be periodically used to execute a batch of tasks. <br/> * It provides a convenience {@link #awaitBatchCompletion()} method that waits until the task queue is empty * <p> * Creation date: May 19, 2011 - 1:24:46 PM * * @author Victor Pillac, <a href="http://uniandes.edu.co">Universidad de Los Andes</a>-<a * href="http://copa.uniandes.edu.co">Copa</a> <a href="http://www.emn.fr">Ecole des Mines de Nantes</a>-<a * href="http://www.irccyn.ec-nantes.fr/irccyn/d/en/equipes/Slp">SLP</a> * @version 1.0 */ public class BatchThreadPoolExecutor extends ThreadPoolExecutor { /** A flag that will activate a debug mode in which all tasks will be run sequentially in the parent thread */ public static boolean sDebugSequential = false; private final AtomicInteger mActiveCount; private final Lock mLock; private final Condition mCondition; /** * Creates a new <code>BatchThreadPoolExecutor</code> * * @param size * the size of the thread pool * @param poolName * a name for the threads of the pool */ public BatchThreadPoolExecutor(int size, String poolName) { this(size, new NameThreadFactory(poolName)); } /** * Creates a new <code>BatchThreadPoolExecutor</code> * * @param size * the size of the thread pool * @param factory * the thread factory that will be used to create new threads */ public BatchThreadPoolExecutor(int size, ThreadFactory factory) { super(sDebugSequential ? 1 : size, sDebugSequential ? 1 : size, 1l, TimeUnit.MINUTES, new LinkedBlockingQueue<Runnable>(), factory); mActiveCount = new AtomicInteger(0); mLock = new ReentrantLock(); mCondition = mLock.newCondition(); } /** * Submit a batch of tasks and wait for their termination. * <p> * The {@link Future} results should then be checked for exception handling (see {@link Future#get()}) * </p> * * @param <C> * The type of callable * @param <V> * The result type returned by the {@link Future}'s {@link Future#get() get} method * @param batch * the collection of tasks to be executed * @param waitForCompletion * <code>true</code> if the calling thread should be suspended until all tasks have been executed * @return a mapping between tasks and their result * @throws InterruptedException */ public <C extends Callable<V>, V> Map<C, Future<V>> submitBatch(Collection<C> batch, boolean waitForCompletion) throws InterruptedException { Map<C, Future<V>> futures = new HashMap<C, Future<V>>(); if (sDebugSequential) { for (C c : batch) { DummyFuture<V> f = new DummyFuture<V>(c); futures.put(c, f); f.run(); } } else { for (C c : batch) { futures.put(c, submit(c)); } if (waitForCompletion) { for (Future<V> f : futures.values()) { try { f.get(); } catch (ExecutionException e) { // Do nothing } } } } return futures; } @Override public void execute(Runnable command) { mLock.lock(); mActiveCount.incrementAndGet(); mLock.unlock(); super.execute(command); } @Override protected void beforeExecute(Thread t, Runnable r) { super.beforeExecute(t, r); } @Override protected void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); mLock.lock(); if (mActiveCount.decrementAndGet() == 0) { mCondition.signalAll(); } mLock.unlock(); } /** * Wait for the completion of the current batch of tasks. <br/> * This method assumes that all tasks were added sequentially by the same {@link Thread} that will call this method. * <p> * It is recommended to use {@link #submitBatch(Collection, boolean)} instead * </p> * * @throws InterruptedException */ public void awaitBatchCompletion() throws InterruptedException { if (isTerminated()) // Already terminated return; if (isShutdown()) // Delegate to parent method if already shutdown awaitTermination(1, TimeUnit.HOURS); // Lock and wait until there is no active task mLock.lock(); while (!isBatchComplete()) { try { mCondition.await(); } catch (InterruptedException e) { mLock.unlock(); throw e; } } mLock.unlock(); } /** * Returns <code>true</code> if the current batch of tasks have completed * * @return <code>true</code> if the current batch of tasks have completed */ public boolean isBatchComplete() { mLock.lock(); boolean r = mActiveCount.get() == 0; mLock.unlock(); return r; } @Override protected void finalize() { super.finalize(); } /** * <code>NameThreadFactory</code> is a simple implementation of {@link ThreadFactory} with custom thread names * <p> * Creation date: May 19, 2011 - 2:07:26 PM * * @author Victor Pillac, <a href="http://uniandes.edu.co">Universidad de Los Andes</a>-<a * href="http://copa.uniandes.edu.co">Copa</a> <a href="http://www.emn.fr">Ecole des Mines de Nantes</a>-<a * href="http://www.irccyn.ec-nantes.fr/irccyn/d/en/equipes/Slp">SLP</a> * @version 1.0 */ public static class NameThreadFactory implements ThreadFactory { static final AtomicInteger sPoolNumber = new AtomicInteger(1); private final ThreadGroup mGroup; private final AtomicInteger mThreadNumber = new AtomicInteger(1); private final String mNamePrefix; /** * Creates a new <code>NameThreadFactory</code> * * @param name * the name of the pool */ public NameThreadFactory(String name) { SecurityManager s = System.getSecurityManager(); mGroup = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); mNamePrefix = name + "-"; } @Override public Thread newThread(Runnable r) { Thread t = new Thread(getGroup(), r, getNamePrefix() + getThreadNumber().getAndIncrement(), 0); if (t.isDaemon()) t.setDaemon(false); if (t.getPriority() != Thread.NORM_PRIORITY) t.setPriority(Thread.NORM_PRIORITY); return t; } /** * Getter for <code>group</code> * * @return the group */ public ThreadGroup getGroup() { return mGroup; } /** * Getter for <code>namePrefix</code> * * @return the namePrefix */ public String getNamePrefix() { return mNamePrefix; } /** * Getter for <code>threadNumber</code> * * @return the threadNumber */ public AtomicInteger getThreadNumber() { return mThreadNumber; } } /** * <code>DummyFuture</code> is an implementation of {@link Future} for tasks that are executed in a synchronous * manner. * <p> * Creation date: Jun 21, 2011 - 11:49:45 AM * * @author Victor Pillac, <a href="http://uniandes.edu.co">Universidad de Los Andes</a>-<a * href="http://copa.uniandes.edu.co">Copa</a> <a href="http://www.emn.fr">Ecole des Mines de Nantes</a>-<a * href="http://www.irccyn.ec-nantes.fr/irccyn/d/en/equipes/Slp">SLP</a> * @version 1.0 * @param <F> */ protected static class DummyFuture<F> implements Future<F>, Runnable { private F mResult = null; private final Runnable mRun; private final Callable<F> mCall; private ExecutionException mException; protected DummyFuture(Runnable run) { mRun = run; mCall = null; } protected DummyFuture(Callable<F> call) { mRun = null; mCall = call; } @Override public void run() { try { if (mCall != null) mResult = mCall.call(); else mRun.run(); } catch (Exception e) { mException = new ExecutionException(e); } } @Override public boolean cancel(boolean mayInterruptIfRunning) { return false; } @Override public boolean isCancelled() { return false; } @Override public boolean isDone() { return true; } @Override public F get() throws InterruptedException, ExecutionException { if (mException != null) throw mException; return mResult; } @Override public F get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { if (mException != null) throw mException; return mResult; } } }