TaskRunner — A Simple Ordered Concurrent Taskset Executor Utility for Java

Vimukthi Wickramasinghe
Vimukthi in Cyberspace
4 min readJan 12, 2013

--

As a Java programmer how many times have you felt the need to have a way of executing a set of tasks(or more commonly ‘ Runnable ‘ s) so that some of them are executable concurrently and some sequentially? For example say you have 5 tasks you need to execute so that,

  • Every other task depends on task-1 being finished. So it needs to run first.
  • task-2 and task-3 are independent. So they can be run concurrently.
  • task-4 and task-5 are also independent but they both depend on task-2 and task-3 being completed.

Today I thought I’d solve this problem by coding something simple and light for me to use. And this blog post is about the thing I came up with( https://github.com/vimukthi-git/TaskRunner). The first requirement is to define a task so that the executor can identify the order of execution of the task. This is done by extending the Runnable interface to make it an orderly Task.

public interface Task extends Runnable {
public String getTaskId();

public Integer getOrder();
}

By defining a task this way the executor can determine order of execution as well as how to group the task sets which can run concurrently. Next the data structure which will hold the task sets to be executed called TaskMap.

public class TaskMap extends TreeMap<Integer, Set> {
private int taskSetCount;

public void addTask(Task task) {
if (containsKey(task.getOrder())) {
get(task.getOrder()).add(task);
} else {
taskSetCount++;
Set temp = new HashSet();
temp.add(task);
put(task.getOrder(), temp);
}
}

public int getTaskSetCount() {
return taskSetCount;
}
}

Only thing to note here is that the addTask method groups and orders Tasks using the getOrder() method in the Task interface. Ordering of task sets happens automatically in a TreeMap because it’s a sorted map. A Set structure is used to group the tasks by their order of execution inside the TreeMap. Having this data structure in place we can move on to the part where the actual execution takes place, the TaskRunner class.

public class TaskRunner {
private TaskMap map;

public TaskRunner(TaskMap map) {
super();
this.map = map;
}

public void run() throws InterruptedException, ExecutionException {
ExecutorService exec = Executors.newCachedThreadPool();
for (Entry<Integer, Set> entry : map.entrySet()) {
List<Future<?>> futures = new ArrayList<Future<?>>();
for (Task task : entry.getValue()) {
futures.add(exec.submit(task));
}
for (Iterator<Future<?>> iterator = futures.iterator(); iterator.hasNext(); ) {
Future<?> future = (Future<?>) iterator.next();
if (future.get() == null) ;
}
}
exec.shutdown();
}
}

Here it takes in a TaskMap to be executed in the constructor. The execution happens in the run method where the TaskMap is iterated to extract and submit each set of tasks to the ExecutorService instance. A Future for each task in a task set is saved temporarily to make sure that all task in the set have finished executing before the next set of tasks in the TaskMap is submitted to the ExecutorService. Now the aforementioned problem of executing the 5 tasks can be solved as follows.

First create the 5 tasks using the Task interface.

TaskMap tasks = new TaskMap();  // Task 1  Task task1 = new Task() {    public void run() {    System.out.println(getTaskId() + " started");    try {     // this is the mock runtime of the task     TimeUnit.SECONDS.sleep(2);    } catch (InterruptedException e) {     e.printStackTrace();    }    System.out.println(getTaskId() + " ended");   }    public String getTaskId() {    return "Task-1";   }    public Integer getOrder() {    return 1;   }  };  tasks.addTask(task1);   // Task 2  Task task2 = new Task() {    public void run() {    System.out.println(getTaskId() + " started");    try {     // this is the mock runtime of the task     TimeUnit.SECONDS.sleep(2);    } catch (InterruptedException e) {     e.printStackTrace();    }    System.out.println(getTaskId() + " ended");   }    public String getTaskId() {    return "Task-2";   }    public Integer getOrder() {    return 2;   }  };  tasks.addTask(task2);   // Task 3  Task task3 = new Task() {    public void run() {    System.out.println(getTaskId() + " started");    try {     // this is the mock runtime of the task     TimeUnit.SECONDS.sleep(2);    } catch (InterruptedException e) {     e.printStackTrace();    }    System.out.println(getTaskId() + " ended");   }    public String getTaskId() {    return "Task-3";   }    public Integer getOrder() {    return 2;   }  };  tasks.addTask(task3);

Above code code demonstrates the creation of first 3 tasks. I have made the tasks sleep for 2 seconds in the run method to simulate running them. Note how the getOrder() method returns 1 for the Task-1 to make it run first. Then in the tasks 2 and 3 it returns 2 to make them run concurrently after Task-1. Same can be applied to tasks 4 and 5 where they’ll return 3 as the order to make them run concurrently after tasks 2 and 3. After having the TaskMap prepared the running of the tasks is easy.

TaskMap tasks = createTaskMap(); TaskRunner runner = new TaskRunner(tasks); long startTime = System.currentTimeMillis(); try { runner.run(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } long stopTime = System.currentTimeMillis(); long elapsedTime = stopTime - startTime; System.out.println("Time taken - " + elapsedTime / 1000 + " seconds");

Just submit the TaskMap to a TaskRunner and call it’s run method. Complete example can be downloaded from here.

--

--