Asynchronous in Java 8

CompletableFuture

It is used to achieve asynchronous computation. Here I have created two different thread pool one for IO bound and another for CPU bound operations. We can use different thread pool for different operations using static methods provided in CompletableFuture. By default it uses ForkJoin Thread Pool. CompletableFuture can perform asynchronously letting main thread doing other tasks.

public class CompletableFutureExample {

    public static void main(String[] args) throws ExecutionException {
        ExecutorService poolForCPUBound = Executors.newFixedThreadPool(5);
        ExecutorService poolForIOBound = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            String orderNo = "order " + i;
            CompletableFuture.supplyAsync(() -> getOrder(orderNo))
                    .thenApplyAsync(order -> addToCart(order), poolForCPUBound)
                    .thenApply(order -> checkOut(order))
                    .thenApply(order -> performPayment(order))
                    .exceptionally(order -> new RuntimeException("payment failed"))
                    .thenAcceptAsync(order -> sendEmail(order), poolForIOBound);
        }
        System.out.println("main thread can do other tasks");
    }

    public static String getOrder(final String orderNo) {
        return orderNo;
    }

    public static Object addToCart(Object order) {
        //perform IO (database) call
        return order + "->cart";
    }

    public static Object checkOut(Object order) {
        return order + "->checkout";
    }

    public static Object performPayment(Object order) {
        if (order.toString().contains("2"))
            throw new RuntimeException();  //for exception check
        return order + "->payment";
    }

    public static void sendEmail(Object order) {
        //send http request
        System.out.println("email sent: " + order);
    }
}

CompletableFuture.get()

The get() method is a blocking method, which means that it will wait for the result to become available if it is not already. If the computation has already completed, the get() method will return the result immediately. If the computation has not yet completed, the get() method will block until the result is available.

ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> futureResult = executor.submit(() -> {
    // perform some long-running computation and return a String
    return "result";
});
String result = futureResult.get(); // block until result is available
executor.shutdown();

Why Executer.shutDown()?

It is recommended to use the executor.shutdown() method after submitting tasks to an ExecutorService. The reason is that the ExecutorService creates and manages a pool of threads, and these threads continue to run even after the tasks have completed, unless the ExecutorService is explicitly shutdown.

If you do not shut down the ExecutorService, the threads in the pool will continue to consume resources such as CPU time and memory, even if they are not performing any useful work. This can lead to resource leaks and can potentially cause performance issues or even crashes if the pool grows too large.