Task Orchestration is the process of managing and controlling the execution of multiple tasks to ensure that theyCorrect execution in a predetermined order。
1. Why do we need task scheduling?
In complex business scenarios, there are usually dependencies between tasks, i.e., a task will depend on the execution result of another task, in which case we need to ensure that the tasks are executed in the correct order through task orchestration.
For example, the order of execution of the following tasks:
Of these, task two will not be executed until task one has been completed, while task four will not be executed until both task two and task three have been completed.
2. Task scheduling implementation
The main means of task organization and control are the following:
- Future
- CompletableFuture
- CountDownLatch
- Semaphore
- CyclicBarrier
However, in the case of a global thread pool, you can only use Future or CompletableFuture if you want to achieve precise task scheduling.
2.1 Future task organization
Use Future to implement the scheduling of the above four tasks (task two can't be executed until task one has finished executing, and task four can't be executed until both task two and task three have finished executing):
import .*;
import ;
public class TaskOrchestrator {
public static void main(String[] args) {
// Create a thread pool to perform tasks
ExecutorService executor = (5);
// Definition of task I
Future<String> taskOneResult = (new Callable<String>() {
@Override
public String call() throws Exception {
(2000); // Simulation of time-consuming operations
return "Task One Result";
}
});
// Definition of task II,Dependency mandate I
Future<String> taskTwoResult = (new Callable<String>() {
@Override
public String call() throws Exception {
String result = (); // Blocking waiting for task one to complete
(1000); // Simulation of time-consuming operations
return "Task Two Result, got: " + result;
}
});
// Definition of task III
Future<String> taskThreeResult = (new Callable<String>() {
@Override
public String call() throws Exception {
(1500); // Simulation of time-consuming operations
return "Task Three Result";
}
});
// Definition of task IV,Dependence on mandates II and III
Future<String> taskFourResult = (new Callable<String>() {
@Override
public String call() throws Exception {
String taskTwoOutput = (); // Blocking waiting for task two to complete
String taskThreeOutput = (); // Blocking waiting for task three to be completed
(500); // Simulation of time-consuming operations
return "Task Four Result, got: " + taskTwoOutput + " and " + taskThreeOutput;
}
});
// Print the final result
try {
("Final Result: " + ());
} catch (InterruptedException | ExecutionException e) {
();
}
}
}
2.2 CompletableFuture Task Orchestration
There are many methods provided by CompletableFutrue, but the most common and useful core methods are only the following:
Next, use CompletableFuture to implement the scheduling of the four tasks above (task two can't be executed until task one is finished, and task four can't be executed until both task two and task three are finished):
import ;
import ;
public class CompletableFutureExample {
public static void main(String[] args) {
// Task 1: return "Task 1 result"
CompletableFuture<String> task1 = (() -> {
try {
// Simulate a time-consuming operation
(1000); }
} catch (InterruptedException e) {
().interrupt(); } catch (InterruptedException e) {
throw new RuntimeException(e); } catch (InterruptedException e) { ().
}
return "Task 1 result";
}).
// Task 2: depends on Task 1, returns "Task 2 result" + result of Task 1
CompletableFuture<String> task2 = ((result1, throwable) -> {
try {
// Simulate a time-consuming operation
(1000); } catch (InterruptedException e.g.)
} catch (InterruptedException e) {
().interrupt();
throw new RuntimeException(e); } catch (InterruptedException e) { ().
}
return "Task 2 result " + result1;
}).
// Task 3: execute in parallel with Task 1 and Task 2, return "Task 3 result"
CompletableFuture<String> task3 = (() -> {
try {
// Simulate a time-consuming operation
(800); // task3 may finish before task2
} catch (InterruptedException e) {
().interrupt(); } catch (InterruptedException e) {
throw new RuntimeException(e); } catch (InterruptedException e) { ().
}
return "Task 3 result";
}).
// Task 4: depends on Task 2 and Task 3, waits for them to complete and then executes them, returning "Task 4 result" + the results of Task 2 and Task 3
CompletableFuture<String> task4 = (task2, task3).handle((res, throwable) -> {
try {
// There's no need to explicitly wait here, because allOf already guarantees that they're done.
return "Task 4 result with " + () + " and " + ();"; } catch (InterruptedException)
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e); } catch (InterruptedException | ExecutionException e) {
}
}).
// Get the result of task four and print it
String finalResult = ();
(finalResult);
}
}
Post-lesson Reflections
What is the relationship between Future and CompletableFutrue and how is CompletableFutrue implemented underneath?
This article has been included in my interview mini-site, which contains modules such as Redis, JVM, Concurrency, Concurrency, MySQL, Spring, Spring MVC, Spring Boot, Spring Cloud, MyBatis, Design Patterns, Message Queuing and more.