Concurrency and Improvements in Java 8: Part 1

Dec 11, 2019
hackajob Staff

When Java 8 was introduced, lambda expressions and the Stream API were among the most talked about features in this release. However, Java 8 has also added a class called ‘CompletableFuture’ in the Concurrency API which is one of the lesser-known but most significant features introduced. In this two-part article, we’ll be covering the ‘CompletableFuture’ class in detail and we’ll start by introducing you to concurrent programming in Java.

Java 8 added the ‘CompletableFuture’ class in the ‘java.util.concurrent’ package, with the intention to support asynchronous programming. Enabling you to write code in a separate thread so the main thread can continue, it can also be utilised to return a result from a thread and attach callback methods which execute once said thread is completed.

A brief history of concurrent programming in Java

Before we dive into the ‘CompletableFuture’ class, it’s necessary to understand how multi-threaded programming worked in Java prior to the new features added in the latest release, including any shortcomings, as well as how the new class addresses this.

The Runnable interface

Java offers the ‘Runnable’ interface to support multi-threading. This interface provides the run method which executes code in a separate thread. The downside of using ‘Runnable’? The main thread has no way of knowing that the run method has completed, meaning it can’t be used to return a result to the main method.

Callable and Future

In order to overcome this issue in the ‘Runnable’ interface, Java 5 introduced the ‘Callable’ interface. ‘Callable’ is similar to ‘Runnable’ and can be used to spawn a new thread, as well as return the result of a computation. In addition to the ‘Callable’ interface, Java 5 also added the ‘Executor’ framework and the ‘Future’ interface. What’s more, ‘Callable’ can be used with the ‘Executor’ framework to return the result of a computation via a ‘Future’ instance. A ‘Future’ is nothing but a result of an asynchronous computation that’ll be available sometime in the future. The following code demonstrates how this works:

Callable<String> callable = () -> {

System.out.println("Entered Callable");

Thread.sleep(1000);

return "Hello";

};

System.out.println("Starting new Thread");

ExecutorService executorService = Executors.newSingleThreadExecutor();

Future<String> future = executorService.submit(callable);

System.out.println("Doing something else");

System.out.println("Retrieve thread result");

String result = future.get(); // this blocks till result is available

System.out.println("Result is:" + result);

executorService.shutdown();

In the example above, a new ‘Callable’ instance is created with a lambda expression being used to provide an implementation for the ‘Call’ method - this returns a ‘String’ result and a new ‘ExecutorService’ is created.

Please note that ‘ExecutorService’ is part of the ‘Executor’ framework, but explaining it in detail would be beyond the scope of this article. Instead, we recommend checking out some Executor resources in your spare time.

The ‘ExecutorService.submit’ method is invoked with the ‘Callable’ instance which spawns a new Thread. From there, the ‘ExecutorService.submit’ method returns a ‘Future’ instance which holds the result of the computation and has a further method called ‘get’ which waits for the thread to complete and will then return the following output:

Starting new Thread

Doing something else

Retrieve thread result

Entered Callable

Result is:Hello

Common Disadvantages of Future

Although the ‘Future’ interface enables you to return a result from a Thread, it has several disadvantages including:

·       The 'get' method in the 'Future' interface is blocking, so if the result of computation is complete, it returns immediately. Otherwise, it'll wait till the computation is completed.  If you want to perform some actions on the result of the computation, you'll need to wait till the result is available.

·       There's no way to manually complete the task. So if the task is hung, there is no way of manually marking it as complete.

·       Multiple 'Futures' cannot be chained or combined.

·       The 'Future' interface does not support any exception handling mechanism.

To solve all these issues, the ‘CompletableFuture’ class was introduced by Java 8.

CompletableFuture

The ‘CompletableFuture’ class implements the ‘Future’ interface. In addition, it also implements the ‘CompletionStage’ interface and has several features that help to overcome any shortcomings from the ‘Future’ interface. Before diving into these features, it’s crucial to first understand some basics about the ‘CompletableFuture’ class:

Creating CompletableFuture

A ‘CompletableFuture’ can be created as follows:

CompletableFuture<String> result = new CompletableFuture<String>();

The example above is the most basic way of creating a ‘CompletableFuture’. If you’re sure about the result of a ‘CompletableFuture’, you can also make it as follows:

CompletableFuture<String> completableFuture1 = CompletableFuture.completedFuture("Hello");

This creates a ‘CompletableFuture’ that returns the String result “Hello”.

Note that creating a ‘CompletableFuture’ like the example above isn’t very useful because it doesn’t spawn a new Thread.

CompletableFuture.get

Just like the ‘Future’ class, ‘CompletableFuture’ has a method called ‘get’ which can be used to obtain the result of computation. The ‘get’ method blocks just like ‘Future.get()’.

As an example, if you create a ‘CompletableFuture’ like so:

CompletableFuture<String> result = new CompletableFuture<String>();

and invoke ‘result.get’, the above example will block forever because there is no thread to complete and therefore no result to return. Ultimately, the ‘CompletableFuture.get’ method should only be used to obtain the result when code runs asynchronously as explained below.

Running Code Asynchronously

As seen earlier, a ‘Future’ is used with the ‘Executor’ framework in order to run tasks asynchronously, whereas ‘CompletableFuture’ has two static methods which can be used to run tasks out of sync. These are as explained below:

runAsync

The ‘runAsync’ method can be used to spawn a new thread and run code asynchronously, whilst accepting a ‘Runnable’ instance as a parameter. It doesn’t return any result and so instead returns a ‘CompletableFuture<Void>’. Overall, this method should be used when the thread doesn't return any value, with the following code demonstrating how this works:

Runnable runnable = () -> System.out.println("Threading saying Hello");

System.out.println("Starting new Thread from Main");

CompletableFuture<Void> cFuture = CompletableFuture.runAsync(runnable);

System.out.println("Main Doing something else");

System.out.println("Main completed");

Here, a new ‘Runnable’ instance is created and implemented via a lambda expression. The ‘CompletableFuture.runAsync’ method is invoked using the ‘Runnable instance’, which spawns a new Thread and executes the ‘Sysout’ statement. When this code is executed, it'll print the following output:

Starting new Thread from Main

Main Doing something else

Main completed

Threading saying Hello

Note that the sequence of execution may vary each time and so the output will differ accordingly.

supplyAsync

The ‘supplyAsync’ method is similar to the ‘runAsync’ method and can be used to spawn a new Thread and run code at the same time, with the only difference being that it can be used to return a value from the Thread. Accepting a ‘Supplier’ instance as a parameter, this works to supply the return value and returns a ‘CompletableFuture<T>’ that corresponds to the return value which is produced by the ‘Supplier’ instance. The following code demonstrates this method:

public class SupplyAsyncDemo {

public static void main(String[] args) throws InterruptedException, ExecutionException {

Supplier<String> supplier = () -> "hello";

System.out.println("Starting new Thread from main");

CompletableFuture<String> cFuture = CompletableFuture.supplyAsync(supplier);

System.out.println("Main Doing something else");

System.out.println("Blocking and retrieving result in Main");

String result = cFuture.get();

System.out.println("Result is "+result);

System.out.println("Main thread completed");

}

}

Here, a ‘Supplier’ is created that returns the String “hello”, which is passed to the ‘CompletableFuture.supplyAsync’ method. This spawns a new Thread that returns the value produced by the ‘Supplier’. The Main thread calls the ‘cFuture.get’ to retrieve the result from the thread and the call will block until the Thread is completed and returns the result. This code should print the following output:

Starting new Thread from Main

Main Doing something else

Blocking and retrieving result in Main

Result is hello

Main thread completed

Again, the sequence of execution may vary each time and so the output will differ accordingly.

RunAsync and supplyAsync with Executors

The ‘runAsync’ and ‘supplyAsync’ methods demonstrated above get a Thread from the ‘ForkJoinPool.commonPool(which is a global Thread pool). It’s key to remember that there are overloaded versions of these methods that accept an ‘Executor’ instance and obtain Threads from the passed executor. The code below shows how this works:

Runnable runnable = () -> System.out.println("Threading saying Hello");

System.out.println("Starting new Thread from Main");

CompletableFuture<Void> cFuture = CompletableFuture.runAsync(runnable,Executors.newSingleThreadExecutor());

System.out.println("Main Doing something else");

System.out.println("Main completed");

Here, in addition to the ‘Runnable’ instance, the ‘runAsync’ method accepts an argument as the ‘Executor’ instance, meaning that ‘Runnable’ is executed by the ‘Executor’ passed in. Similarly, there’s also an overloaded version of the ‘supplyAsync’ method that accepts an Executor instance overall.

In this article, we briefly discussed the history of concurrent programming in Java as well as the ‘CompletableFuture’ class. In part 2 of this article, we’ll be covering the ins and outs of the ‘CompletableFuture’ class, including the features and benefits.