Asynchronous Programming in Java - Ideas2IT

Asynchronous Programming in Java

Share This

Programming today is getting more and more complex. You can no longer single-handedly build a full-fledged application. Large and small teams work together to develop applications with extensive features and functionalities. While many methods of collaboration and team-building have evolved to help teams work together, coding languages and methodologies have evolved as well to adapt to collaboration. Java is one of the oldest and yet, a very versatile coding language. To enable developers to build functionalities and features in Java parallelly, there is a method called Asynchronous programming.

Asynchronous programming is a technique for parallel programming that lets teams distribute work and build application features separately from the primary application thread. When the team is ready with the feature, the code is synced with the main thread. There are numerous benefits of Asynchronous programming like improved application performance and enhanced responsiveness.

Backend engineers always face situations where they need to process data asynchronously in Java to build quality applications efficiently.

There are multiple ways to do Async programming in Java, starting from Thread, Runnable, Callable<T>, Future<T> (and its extended ScheduledFuture<T>), CompletableFuture<T>, and of course, ExecutorService and ForkJoinPool.

Thread

Threadis a very basic yet powerful component in Java concurrency. It is actually associated with the Thread of the Operating System. The very basic way to create a Thread is by extending it and overriding the run method.

public class TestThread extends
Thread{
    @Override
    public void run()
    {
      // Logic

      super.run();
    }
}
TestThread t = new TestThread();
// starts thread
t.start();// starting the thread, causes the run method be called

Starting the Threadcauses the run() method to be called.

But as you can notice, Thread has other methods that can be overridden:

  • In most cases, we don’t want to override other methods of the thread. 
  • Once we extend the Thread class, the extending class loses its ability to extend further as Java does not support multiple inheritances.
  • Each thread has its own object when we extend it, and it’s not good for memory health when there are several Objects of the extended Thread created.

Java addresses these issues with the Runnable interface. In fact, Thread has an overloaded method that takes Runnable.

Runnable

Runnable is an interface that has only one method: run(). Yes, Runnable is a functional interface, and its instance can be created with the lambda function. The process is a little detailed for implementing this for complex functionalities. You can clearly notice the difference in the example below.

// With lambda
Runnable runnable = ()->System.out.println("I'm a runnable from lambda.");

// With implemention, we can hold the data and related stuff that we want to process.
// Otherwise we got to manage them in the launching thread
public class RunnableImplemented implements Runnable{

    List<Object> mayBeAListOfData;

    Object mayBeAService;

    Object mayBeADao;

    public RunnableImplemented(List<Object> mayBeAListOfData, 
 
    Object mayBeAService, Object mayBeADao) {
      
        super();

        this.mayBeAListOfData = mayBeAListOfData;

        this.mayBeAService = mayBeAService;

        this.mayBeADao = mayBeADao;

    }

    @Override

    public void run() {

        // code logic

    }

}

Though Runnable has a run()method, it’s not a Thread but just a Java class until it is taken control of by (passed to) Thread. The starting of the thread causes the runnable object’s run()method to be called.

public class TestThread {

   private static Runnable runnable = ()->System.out.println("I'm a runnable from lambda.");

   public static void main(String[] args) {

      Thread t = new Thread(runnable);// takes runnable here

      t.start();

   }

Here, you may have noticed that,

  • The Runnable method doe not return anything
  • It does not have a proper handling method for exceptions. You have to surround your code that throws an Exception with try and catch block

To address these issues, Java came up with Callable<T> in version 1.5.

Callable<T>

Callable is a generic interface. This is because the type of return value is generic.  Callable is a functional interface and call() is the only no-argument method that returns a generic type value.

The implementation of Callable is very similar to Runnable:

private static Callable<Integer> callable = ()-> {

 String data = "I'm in callable.";

 System.out.println();

 return data.length();

};

public static void main(String[] args) {

 ExecutorService executor = Executors.newSingleThreadExecutor();

 Future<Integer> callFuture = executor.submit(callable);

 Integer integer = callFuture.get();

}

As shown in the above code, the call method processes the data and returns a value that can be collected post-execution. But, is there a huge difference in invoking it. We use ExecutorService to invoke and Future to hold the result.

This is required because there is no controlled behavior of creating and running any of the functions — Threads, Runnable or Callable. But, the teams need to have control over the number of threads running at a time as each of them is associated with the threads of the OS. The number of Threads running should be lesser than the number of available CPU cores. All together, Java solves it through the ExecutorService interface.

Leave a Reply

Your email address will not be published. Required fields are marked *

Get Instant Pricing Straight to Your Inbox

Let us know what your needs are and get a quote sent straight to your inbox.

Thanks for subscribing

Get Pricing Sent Straight to Your Inbox