Follow these steps to implement the example:
- First, we're going to implement the auxiliary tasks we will use in the example. Create a class named SeedGenerator that implements the Runnable interface. It will have a CompletableFuture object as an attribute, and it will be initialized in the constructor of the class:
public class SeedGenerator implements Runnable {
private CompletableFuture<Integer> resultCommunicator;
public SeedGenerator (CompletableFuture<Integer> completable) {
this.resultCommunicator=completable;
}
- Then, implement the run() method. It will sleep the current thread for 5 seconds (to simulate a long operation), calculate a random number between 1 and 10, and then use the complete() method of the resultCommunicator object to complete CompletableFuture:
@Override
public void run() {
System.out.printf("SeedGenerator: Generating seed...\n");
// Wait 5 seconds
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
int seed=(int) Math.rint(Math.random() * 10);
System.out.printf("SeedGenerator: Seed generated: %d\n",
seed);
resultCommunicator.complete(seed);
}
- Create a class named NumberListGenerator that implements the Supplier interface parameterized with the List<Long> data type. This means that the get() method provided by the Supplier interface will return a list of large numbers. This class will have an integer number as a private attribute, which will be initialized in the constructor of the class:
public class NumberListGenerator implements Supplier<List<Long>> {
private final int size;
public NumberListGenerator (int size) {
this.size=size;
}
- Then, implement the get() method that will return a list with millions of numbers, as specified in the size parameter of larger random numbers:
@Override
public List<Long> get() {
List<Long> ret = new ArrayList<>();
System.out.printf("%s : NumberListGenerator : Start\n",
Thread.currentThread().getName());
for (int i=0; i< size*1000000; i++) {
long number=Math.round(Math.random()*Long.MAX_VALUE);
ret.add(number);
}
System.out.printf("%s : NumberListGenerator : End\n",
Thread.currentThread().getName());
return ret;
}
- Finally, create a class named NumberSelector that implements the Function interface parameterized with the List<Long> and Long data types. This means that the apply() method provided by the Function interface will receive a list of large numbers and will return a Long number:
public class NumberSelector implements Function<List<Long>, Long> {
@Override
public Long apply(List<Long> list) {
System.out.printf("%s: Step 3: Start\n",
Thread.currentThread().getName());
long max=list.stream().max(Long::compare).get();
long min=list.stream().min(Long::compare).get();
long result=(max+min)/2;
System.out.printf("%s: Step 3: Result - %d\n",
Thread.currentThread().getName(), result);
return result;
}
}
- Now it's time to implement the Main class and the main() method:
public class Main {
public static void main(String[] args) {
- First, create a CompletableFuture object and a SeedGenerator task and execute it as a Thread:
System.out.printf("Main: Start\n");
CompletableFuture<Integer> seedFuture = new CompletableFuture<>();
Thread seedThread = new Thread(new SeedGenerator(seedFuture));
seedThread.start();
- Then, wait for the seed generated by the SeedGenerator task, using the get() method of the CompletableFuture object:
System.out.printf("Main: Getting the seed\n");
int seed = 0;
try {
seed = seedFuture.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
System.out.printf("Main: The seed is: %d\n", seed);
- Now create another CompletableFuture object to control the execution of a NumberListGenerator task, but in this case, use the static method supplyAsync():
System.out.printf("Main: Launching the list of numbers
generator\n");
NumberListGenerator task = new NumberListGenerator(seed);
CompletableFuture<List<Long>> startFuture = CompletableFuture
.supplyAsync(task);
- Then, configure the three parallelized tasks that will make calculations based on the list of numbers generated in the previous task. These three steps can't start their execution until the NumberListGenerator task has finished its execution, so we use the CompletableFuture object generated in the previous step and the thenApplyAsync() method to configure these tasks. The first two steps are implemented in a functional way, and the third one is an object of the NumberSelector class:
System.out.printf("Main: Launching step 1\n");
CompletableFuture<Long> step1Future = startFuture
.thenApplyAsync(list -> {
System.out.printf("%s: Step 1: Start\n",
Thread.currentThread().getName());
long selected = 0;
long selectedDistance = Long.MAX_VALUE;
long distance;
for (Long number : list) {
distance = Math.abs(number - 1000);
if (distance < selectedDistance) {
selected = number;
selectedDistance = distance;
}
}
System.out.printf("%s: Step 1: Result - %d\n",
Thread.currentThread().getName(), selected);
return selected;
});
System.out.printf("Main: Launching step 2\n");
CompletableFuture<Long> step2Future = startFuture
.thenApplyAsync(list -> list.stream().max(Long::compare).get());
CompletableFuture<Void> write2Future = step2Future
.thenAccept(selected -> {
System.out.printf("%s: Step 2: Result - %d\n",
Thread.currentThread().getName(), selected);
});
System.out.printf("Main: Launching step 3\n");
NumberSelector numberSelector = new NumberSelector();
CompletableFuture<Long> step3Future = startFuture
.thenApplyAsync(numberSelector);
- We wait for the finalization of the three parallel steps with the allOf() static method of the CompletableFuture class:
System.out.printf("Main: Waiting for the end of the three
steps\n");
CompletableFuture<Void> waitFuture = CompletableFuture
.allOf(step1Future, write2Future,
step3Future);
- Also, we execute a final step to write a message in the console:
CompletableFuture<Void> finalFuture = waitFuture
.thenAcceptAsync((param) -> {
System.out.printf("Main: The CompletableFuture example has
been completed.");
});
finalFuture.join();