From my main flow I want to do some tasks in parallel. Say I have 4 tasks that I want to execute in a separate flow, but in those 4 tasks, 1st task needs to be completed before I can run other tasks in parallel.
I thinks my code works as per my requirement, but is there a better approach?
public static void main(String[] args) {
System.out.println("In main");
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> {
runParalleltasks();
});
executor.shutdown();
System.out.println("Exiting main");
}
private static void runParalleltasks() {
System.out.println("Running parallel tasks");
doTask1();
ExecutorService executor = Executors.newFixedThreadPool(3);
executor.submit(() -> {
doTask2();
});
executor.submit(() -> {
doTask3();
});
executor.submit(() -> {
doTask4();
});
executor.shutdown();
System.out.println("Exiting parallel tasks");
}
You might want to take a look at the new CompletableFuture
facility. It might not provide a lot of help with a trivial case such as yours but it offers a lot of flexibility:
import static java.util.concurrent.CompletableFuture.allOf;
import static java.util.concurrent.CompletableFuture.runAsync;
runAsync(() -> doTask1(), executor)
.thenCompose(v -> allOf(
runAsync(() -> doTask2(), executor),
runAsync(() -> doTask3(), executor),
runAsync(() -> doTask4(), executor)
));
Where this approach would really shine you is if you needed to pass output of task1 to the dependent tasks. Suppose that doTask1
returns a String
and doTask2
accepts a String
. Now instead of runAsync
for the first task we should use supplyAsync
:
supplyAsync(() -> doTask1(), executor)
.thenCompose(resultOf1 -> allOf(
runAsync(() -> doTask2(resultOf1), executor),
runAsync(() -> doTask3(), executor),
runAsync(() -> doTask4(), executor)
));
You can do this with an ExecutorService
as well, much like your original approach. All you have to do is to use the result of submit
which allows subsequent tasks to wait for its completion:
ExecutorService executor = Executors.newFixedThreadPool(4);
Future<?> task1 = executor.submit(() -> doTask1());
Stream.<Runnable>of(() -> doTask2(), () -> doTask3(), () -> doTask4())
.forEach(r -> executor.submit(() -> { try {
task1.get();
r.run();
} catch(InterruptedException|ExecutionException ex){}
}));
executor.shutdown();
We have to catch exceptions when waiting for the completion of the first task, but this also opens the opportunity to deliberately skip the next task if the first task fails or gets canceled. Since normally, the dependency between tasks exists because the subsequent tasks need the result of the first task, waiting via get()
is the natural way. As soon as doTask1()
returns a value, the submit
invocation above will use submit(Callable)
rather than submit(Runnable)
and the returned Future
will have a generic type reflecting the return type of the method, so the result can be retrieved via its get()
method.
In order to avoid deadlocks, it least one of the following conditions must be true:
In the example, even both apply.
Note that the stream use above is not necessary, a classical loop could do that as well:
for(Runnable r: Arrays.<Runnable>asList(() -> doTask2(), () -> doTask3(), () -> doTask4()))
executor.submit(() -> { try {
task1.get();
r.run();
} catch(InterruptedException|ExecutionException ex){}
});
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.