Last mod: 2024.12.21

Java threads - Executors, Callable and Future

The Callable and Future interfaces were introduced in Java 5, which came out in 2004. They were added as part of the java.util.concurrent library, which was part of a larger upgrade to manage concurrency in Java.

ExecutorService, Callable and Future

Starting a thread:

ExecutorService executor = Executors.newFixedThreadPool(3); // We start a pool of 3 threads.
// Threads exceeding these 3 will wait for a free thread in the pool 
executor.submit(() -> {
    System.out.println("Task in executor");
});

Waiting for result:

ExecutorService executor = Executors.newFixedThreadPool(3);
Future<Double> future = executor.submit(() -> {
    for (int i = 1; i <= 10; i++) { // example of long executing code
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("Thread iteration " + i);
    }
    return Math.random();
});

System.out.println("Result: " + future.get()); // Waiting and read result
executor.shutdown();

Example of callable:

Callable<Double> task = () -> {
    Thread.sleep(1_000);
    return Math.random();
};
Future<Double> future = Executors.newSingleThreadExecutor().submit(task);

System.out.println("Result: " + future.get());

Thread cancellation:

ExecutorService executor = Executors.newFixedThreadPool(3);
Future<Double> future = executor.submit(() -> {
    for (int i = 1; i <= 10; i++) { // example of long executing code
        try {
            Thread.sleep(1_000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("Thread iteration " + i);
    }
    return Math.random();
});

Thread.sleep(5_000L);
future.cancel(true); // Thread cancellation after 5 seconds

Thread.sleep(10_000L);
executor.shutdown();

Asynchronics tasks

CompletableFuture allows for asynchronous operations with the ability to combine them and handle errors:

CompletableFuture.supplyAsync(() -> "Data")
        .thenApply(data -> data + " processed")
        .thenAccept(System.out::println);