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

Asynchronous Programming in Java

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 Threadclass, 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 Threadcreated.

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 Runnablemethod 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 callmethod processes the data and returns a value that can be collected post-execution. But, is there a huge difference in invoking it. We use ExecutorServiceto 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.

Ideas2IT Team

Connect with Us

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