package com.interview.multithreaded; import com.google.common.collect.Lists; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * Given a Task with list of its dependencies and execute method. Run the task such that dependencies are executed first. * You are given x number of threads. Increase parallelism as much as possible. */ public class DependencyTaskExecutor { Map<String, CompletableFuture<Void>> taskTracker = new HashMap<>(); void scheduleTask(List<Task> tasks, int threads) { ExecutorService executor = Executors.newFixedThreadPool(threads); CompletableFuture<Void> future = CompletableFuture.completedFuture(null); for (Task task : tasks) { future = future.thenAcceptBothAsync(scheduleTaskUtil(task, executor), (a, b) -> {}, executor); } future.thenRunAsync(() -> {System.out.println("All tasks done. Closing executor"); executor.shutdown();}); } CompletableFuture<Void> scheduleTaskUtil(Task task, Executor executor) { CompletableFuture<Void> f = taskTracker.get(task.name()); if (f != null) { return f; } if (task.dependencies().isEmpty()) { CompletableFuture<Void> future = CompletableFuture.runAsync(() -> task.execute(), executor); taskTracker.put(task.name(), future); return future; } CompletableFuture<Void> future = CompletableFuture.completedFuture(null);; for (Task upstreamTask : task.dependencies()) { future = future.thenAcceptBothAsync(scheduleTaskUtil(upstreamTask, executor), (a, b) -> {}, executor); } future = future.thenRunAsync(() -> task.execute(), executor); taskTracker.put(task.name(), future); return future; } public static void main(String args[]) { DependencyTaskExecutor taskExecutor = new DependencyTaskExecutor(); SimpleSleepTask a = new SimpleSleepTask("a", 2000); SimpleSleepTask b = new SimpleSleepTask("b", 4000); SimpleSleepTask c = new SimpleSleepTask("c", 6000); SimpleSleepTask d = new SimpleSleepTask("d", 3000); SimpleSleepTask x = new SimpleSleepTask("x", 4000); SimpleSleepTask y = new SimpleSleepTask("y", 6000); SimpleSleepTask z = new SimpleSleepTask("z", 3000); d.addDependency(b); d.addDependency(c); c.addDependency(a); b.addDependency(a); x.addDependency(y); x.addDependency(z); y.addDependency(a); taskExecutor.scheduleTask(Lists.newArrayList(a, b, c, d, x, y, z), 4); } } interface Task { String name(); List<Task> dependencies(); void execute(); } class SimpleSleepTask implements Task { String name; int sleepTimeInMillis; List<Task> dependencies = new ArrayList<>(); SimpleSleepTask(String name, int sleepTimeInMillis) { this.name = name; this.sleepTimeInMillis = sleepTimeInMillis; } void addDependency(Task task) { dependencies.add(task); } @Override public String name() { return name; } @Override public List<Task> dependencies() { return dependencies; } @Override public void execute() { try { System.out.println("Starting sleep for task " + name); Thread.sleep(sleepTimeInMillis); System.out.println("Ending sleep for task " + name); } catch (InterruptedException e) { e.printStackTrace(); } } }