Ideas2IT rewards key players with 1/3rd of the Company in New Initiative.  Read More >
Back to Blogs

The Future Interface - The Best way for Asynchronous Java Programming

Asynchronous programming in Java is a technique for parallel programming that lets teams distribute work and build application features separately from the primary application thread. Then, when the team is ready with the feature, the code is synced with the main thread. The benefits of Asynchronous programming like improved application performance and enhanced responsiveness are known to all. There are multiple ways to do Async programming in Java, starting with Thread, Runnable, Callable<T>, Future<T> (and its extended ScheduledFuture<T>), CompletableFuture<T>, and of course, ExecutorService and ForkJoinPool.

Java concurrency is the functionality that enables asynchronous programming in Java. Concurrency is mainly the ability to run several programs or applications parallelly smoothly. The java.util.concurrent package is the best way to do asynchronous programming. It provides developers with all the tools required for creating concurrent applications in Java. The backbone of this package consists of threads.

This method of using the java.util.concurrent package for enabling concurrency is usually known as the Future Token method or the Java Future interface. Many built-in concurrency utilities in Java are known to return a Java Future object as output, e.g., the Java ExecutorService. The java.util.concurrent package is built on this functionality. A java.util.concurrent.Future represents the result of an asynchronous computation. When an asynchronous task is created, a Java Future object is returned. This Future object functions as a handle to the result of the asynchronous task. Once the asynchronous task completes, the result can be accessed via the Future object returned when the task was started.

To understand how the Java Future interface works, let us dive into the basics of the interface first.

The Java Future Interface

This is the skeleton of the Java Future interface. The code below is a clear explanation of how the Java Future interface works.

Java

public interface Future<V>
{
  V       get();
  V       get(long timeout, TimeUnit unit);
  boolean isCancelled();
  boolean isDone();
  boolean cancel(boolean mayInterruptIfRunning);
}

Java concurrency is the functionality that enables asynchronous programming in Java. Concurrency is mainly the ability to run several programs or applications parallelly smoothly. The `java.util.concurrent` package is the best way to do asynchronous programming. It provides developers with all the tools required for creating concurrent applications in Java. The backbone of this package consists of threads.

This method of using the `java.util.concurrent` package for enabling concurrency is usually known as the Future Token method or the Java Future interface. Many built-in concurrency utilities in Java are known to return a Java Future object as output, e.g., the Java ExecutorService. The `java.util.concurrent` package is built on this functionality. A java.util.concurrent.Future represents the result of an asynchronous computation. When an asynchronous task is created, a Java Future object is returned. This Future object functions as a handle to the result of the asynchronous task. Once the asynchronous task completes, the result can be accessed via the Future object returned when the task was started.

To understand how the Java Future interface works, let us dive into the basics of the interface first.

How the Java Future Interface Works

This is the skeleton of the Java Future interface. The code below is a clear explanation of how the Java Future interface works.

Java

public interface Future<V>
{
  V       get();
  V       get(long timeout, TimeUnit unit);
  boolean isCancelled();
  boolean isDone();
  boolean cancel(boolean mayInterruptIfRunning);
}

Java asynchronous programming is a technique for parallel programming that lets teams distribute work and build application features separately from the primary application thread. Then, when the team is ready with the feature, the code is synced with the main thread. The benefits of Java Asynchronous programming like improved application performance and enhanced responsiveness are known to all. here are multiple ways to do Async programming in Java, starting with Thread, Runnable, Callable<T>, Future<T> (and its extended ScheduledFuture<T>), CompletableFuture<T>, and of course, ExecutorService and ForkJoinPool.

Java concurrency is the functionality that enables asynchronous programming in Java. Concurrency is mainly the ability to run several programs or applications parallelly smoothly. The `java.util.concurrent` package is the best way to do asynchronous programming. It provides developers with all the tools required for creating concurrent applications in Java. The backbone of this package consists of threads.

This method of using the `java.util.concurrent` package for enabling concurrency is usually known as the Future Token method or the Java Future interface. Many built-in concurrency utilities in Java are known to return a Java Future object as output, e.g., the Java ExecutorService. The `java.util.concurrent` package is built on this functionality. A java.util.concurrent.Future represents the result of an asynchronous computation. When an asynchronous task is created, a Java Future object is returned. This Future object functions as a handle to the result of the asynchronous task. Once the asynchronous task completes, the result can be accessed via the Future object returned when the task was started.

To understand how the Java Future interface works, let us dive into the basics of the interface first.

Java

public interface Future<V>
{
  V       get();
  V       get(long timeout, TimeUnit unit);
  boolean isCancelled();
  boolean isDone();
  boolean cancel(boolean mayInterruptIfRunning);
}

Create a Future

You can create a Java Future Token by submitting a task to an ExecutorService. Here, I'm creating a component and initializing an ExecutorService with available core capacity.

Java

public class FutureTutorial
{
  private ExecutorService executorService;
  public FutureTutorial()
  {
      // It's a best practice utilize the maximum core capacity
      // until there is a valid reason to use the lesser value
      executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
  }
}

The Future Interface is a generic interface. This means that the output will also display the type of the return data.  Let's understand it with a Callable function that returns an integer value.

With Callable<T>

Java

public Future<Integer> submitCallable()
{
      Callable<Integer> task = ()->
     {
          System.out.println("I'm a callable");
          return 10;
      };
      Future<Integer> future = executorService.submit(task);
      return future;
  }

Here, We have created a callable task that returns an integer value. The return type of callable is also an integer. So, the generic type of the Future Token obtained by the submission of the callable task is also an integer.

With Runnable

We are all aware that a Callable function returns a value, but a Runnable function does not. So, what type of Future Token is obtained by submitting a Runnable?

As the return type is unknown, the executor returns Future<?>.

Java

public Future<?> submitRunnable()
{
      Runnable task = ()->System.out.println("I'm a runnable");
      Future<?> future = executorService.submit(task);
      return future;
}

Future.isDone()

Once we submit our task and get the Future token, how do we confirm that our task is complete?

Future.isDone() provides the status of the task. It will return a ‘true’ value if this task has been completed. Completion may be due to normal termination, an exception, or cancellation - in all of these cases, this method will return a `true` value.

Java

public Future<Integer> checkResult() throws InterruptedException
{
      Future<Integer> future = submitCallable();
      while (!future.isDone())
     {
          // spin lock to save the cpu cycles
          Thread.sleep(1000);
      }
      return future;
  }

Future.get()

Future.get() is an important function. It is used for getting the final result of the submitted task. But, it serves another purpose as well. If you have a runnable task and just want to check its completion status, you can also do it with the isDone() command. When your task does not return any data, this function works fine. But, when your task is completed with an exception and that exception is stored in the Future token, the only way to get it is Future.get().

If we modify our submitRunnable to produce an unhandled exception, the result will be:

Java

public Future<?> submitRunnable()
{
      Runnable task = ()->
   {
          System.out.println("I'm a runnable");
          // This causes an ArithmeticException
          int res = 10/0;
          System.out.println("This will not be printed due to arithmetic exception.");
      };
      Future<?> future = executorService.submit(task);
      return future;
}

And now I'm calling the checkResult()

Java

public void checkResult() throws InterruptedException
{
      Future<?> future = submitRunnable();
      while (!future.isDone())
     {
          // spin lock to save the cpu cycles
          Thread.sleep(1000);
      }
      System.out.println("Task completed.");
}

With no exceptions, It will complete smoothly. Unless we callFuture.get() we don't see a trace of Arithmetic Exception. It will be a mystery!

Now you know the importance of Future.get(), Let's see how to use it.

Java

public Integer getResult()
{
 Future<Integer> future = submitCallable();

      try
     {
          return future.get();
      }
 catch (InterruptedException e)
     {
          log.error("You task has been interrupted", e);
      }
 catch (ExecutionException e)
     {
          log.error("You task has been completed exceptionally", e);
      }
  return Integer.MIN_VALUE;
}

Future.get() gives 3 exceptions as output:

  1. InterruptedException — In case the task is interrupted
  2. ExecutionException — In case the task is completed exceptionally where you get the exceptions that occurred in the task.
  3. CancellationException —  In case the task is canceled, and you try to call the get(). This extends the RuntimeException (unchecked exception). So it doesn't complain about catching it.

Future.get() With Waiting Period

Future.get()comes with an overloaded method that takes timeout and TimeUnit:

V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;

Now it throws an additional exception, TimeoutException

Java

public Integer getResult()
{
      Future<Integer> future = submitCallable();
      try
      {
          return future.get(1, TimeUnit.SECONDS);
      }
      catch (InterruptedException e)
      {
          log.error("You task has been interrupted", e);
      }
      catch (ExecutionException e)
      {
          log.error("You task has been completed exceptionally", e);
      }
      catch (TimeoutException e)
      {
          log.error("You task has been by Timeout", e);
      }
      return Integer.MIN_VALUE;

  }

Future.cancel (boolean mayInterruptIfRunning)

As per the documents, this function attempts to cancel the execution of this task. This attempt will fail if the task has already been completed, has already been canceled, or could not be canceled for some other reason. If the token has been created successfully or the task has not started when cancel is called, this task will never run. If the task has already begun, then the mayInterruptIfRunning parameter determines whether the thread executing this task should be interrupted in an attempt to stop the task or not.

Future.isCancel()

Returns true if this task was canceled before it was completed normally.

Handling Task Cancellation Programmatically

There are certain cases where a task cannot be canceled at all. It's better to handle cancellation programmatically.

Java

package winkey.articles;
import lombok.extern.slf4j.Slf4j;
/**
* @author Venkatesh Rajendran
*/
@Slf4j

public class TestTask implements Runnable
{

  private boolean interrupt = false;
  private Service service;

  @Override
  public void run()
  {

      service.doStuff(this);
      // Thread checks, if task is interrupted
      if(isInterrupt())
      {
          // task interrupted and post processing is skipped
          log.error("Interrupted politely");
          return;
      }
      // post processing
  }
  public void interrupt()
  {
      interrupt = true;
  }
  public boolean isInterrupt()
  {
      return interrupt;
  }
}

class Service
{
  public void doStuff(TestTask task)
  {
      try
      {
          // doing stuff
      }
      catch (Exception e)
      {
         // In case of exception, we interrupt the task
          task.interrupt();
      }

  }

}

Take your Java projects to the next level with asynchronous programming techniques. Connect with us to streamline your application's performance.

Ideas2IT Team

Connect with Us

We'd love to brainstorm your priority tech initiatives and contribute to the best outcomes.