All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.pvalsecc.concurrent.OrderedResultsExecutor Maven / Gradle / Ivy

package org.pvalsecc.concurrent;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.Collections;
import java.util.LinkedList;
import java.util.Queue;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Take tasks and execute them in //. Each task generates a result and the
 * results are sent to a resultCollector.
 *
 * The guaranties are:
 * 
    *
  • {@link #addTask} is thread safe *
  • the results are sent to the resultCollector in the order their tasks have * been added. *
  • {@link org.pvalsecc.concurrent.OrderedResultsExecutor.ResultCollector#handle(Object)} * is called only one result at a time for a given instance of {@link org.pvalsecc.concurrent.OrderedResultsExecutor.ResultCollector}. * No // call of this method for a given object. *
*/ public class OrderedResultsExecutor { public static Log LOGGER = LogFactory.getLog(OrderedResultsExecutor.class); /** * The base name for the executor threads. */ private final String name; /** * The executor threads. */ private final Thread[] threads; /** * The sequence used to attribute the order of the tasks. */ private AtomicLong nextSequenceNumber = new AtomicLong(0L); /** * Queue of tasks to do. Protected by itself. */ private final Queue> queue; /** * Ordered structure used to store the results the time they are in order. */ private final SortedSet> output = Collections.synchronizedSortedSet(new TreeSet>()); /** * number of the next task to be sent out. Protected by {@link #nextOutputLock} */ private long nextOutput = 1L; /** * The lock to protect {@link #nextOutput}. */ private final Object nextOutputLock = new Object(); public OrderedResultsExecutor(int nbThreads, String name) { this.name = name; this.threads = new Thread[nbThreads]; queue = new LinkedList>(); } /** * Start the executor threads. */ public void start() { for (int i = 0; i < threads.length; i++) { if(threads[i]==null) { Thread thread = threads[i] = new Thread(new Runner(), name+i); thread.setDaemon(true); thread.start(); } } } /** * Stop the executor threads. */ public void stop() { synchronized (queue) { for (int i = 0; i < threads.length; ++i) { //null task means "die!" queue.add(new InternalTask(null, null, 0)); } queue.notifyAll(); } for (int i = 0; i < threads.length; i++) { Thread thread = threads[i]; while (true) { try { thread.join(); break; } catch (InterruptedException e) { //retry } } threads[i]=null; } } /** * Adds a task whose result will be sent to the given resultCollector. */ public void addTask(Task command, ResultCollector resultCollector) { synchronized (queue) { queue.add(new InternalTask(command, resultCollector, nextSequenceNumber.incrementAndGet())); queue.notify(); } } private void addOutput(InternalTask task) { output.add(task); while (true) { InternalTask first; synchronized (nextOutputLock) { if(output.isEmpty()) { //next one not yet available return; } first = output.first(); if (first.sequenceNumber != nextOutput) { //next one not yet available return; } //it wouldn't be a good idea to take the resultCollector lock here. //That would serialize the calls between resultCollectors } //"the dangerous point" synchronized (first.resultCollector) { synchronized (nextOutputLock) { if(first.sequenceNumber !=nextOutput) { //Another thread took over us during "the dangerous point" with the same nextOutputValue continue; } //now we are sure we can output "first" ++nextOutput; output.remove(first); } first.resultCollector.handle(first.result); } } } /** * One executor thread. */ public class Runner implements Runnable { public void run() { if (LOGGER.isDebugEnabled()) LOGGER.debug("Runner [" + name + "] started"); while (true) { //gets a task to be executed InternalTask cur; synchronized (queue) { while ((cur = queue.poll()) == null) { try { queue.wait(); } catch (InterruptedException e) { //ignored } } } if (cur.task == null) { if (LOGGER.isDebugEnabled()) LOGGER.debug("Runner [" + name + "] stopped"); return; //received the signal to stop } //runs it and schedule its result cur.setState(ExecutionState.RUNNING); try { final RESULT process = cur.task.process(); cur.setResult(process); addOutput(cur); } catch (Throwable t) { cur.setState(ExecutionState.ERROR); cur.setError(t); } finally { cur.setState(ExecutionState.DONE); } } } } private enum ExecutionState { PENDING, RUNNING, DONE, ERROR } /** * Internal structure which represents a task and it's related information. * @param */ private static class InternalTask implements Comparable> { private final Task task; private final ResultCollector resultCollector; private final long sequenceNumber; private ExecutionState state; private RESULT result = null; private Throwable error; public InternalTask(Task task, ResultCollector resultCollector, long sequenceNumber) { this.task = task; this.resultCollector = resultCollector; this.sequenceNumber = sequenceNumber; this.state = ExecutionState.PENDING; } public void setResult(RESULT result) { if (this.result != null) { throw new RuntimeException("Synchronization bug"); } this.result = result; } public int compareTo(InternalTask o) { return (sequenceNumber < o.sequenceNumber ? -1 : (sequenceNumber == o.sequenceNumber ? 0 : 1)); } public synchronized void setState(ExecutionState state) { this.state = state; } public synchronized void setError(Throwable error) { this.error = error; } } /** * Definition of a task. */ public static interface Task { /** * Called in parallel and in random order to do the processing. The * implementation must be thread safe. * @return The result of the task. */ RESULT process(); } /** * Definition of a result collector. */ public static interface ResultCollector { /** * Will be called sequentially (no // execution for the same instance) * with each task's result, in the order the task have been scheduled. */ public void handle(RESULT result); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy