Async Programming in Java: Part I
Let's discuss asynchronous programming in Java and various ways to achieve it, starting with Thread, Runnable, and Callable.
Join the DZone community and get the full member experience.
Join For FreeAs a backend engineer, we face situations to process the data asynchronously. Today let's see how it's done in java and various way to do it.
Starting from Thread, Runnable, Callable<T>, Future<T> (and its extended ScheduledFuture<T>), CompletableFuture<T>, and of course, ExecutorService and ForkJoinPool. We will see all of them, but one by one.
Thread
The very basic yet so powerful component of Java concurrency is Thread
. The Thread of Java 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:
x
public class TestThread extends Thread{
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 thread causes the run()
method to be called.
You may ask; yes, Thread has tons of 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 losses 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 tons of 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. Yet it's an easy way to do this; for complex things, we might like to implement it. See the difference here. It's all about the requirement:
x
// 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;
}
public void run() {
// code logic
}
}
Though Runnable has arun()
method, it's not a Thread but just a Java class until it's taken control by (passed to) Thread. The starting of the thread causes the runnable object'srun()
method to be called.
xxxxxxxxxx
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();
}
}
Cool, we learnt how to create threads with Thread as well as Runnable. But have you noticed Thread (or Runnable either),
- The
Runnable
method doesn't return anything? - Also doesn't have exception handling. You have to surround your code that throws an Exception with try and catch block
Yep, Java got it solved in version 1.5, and it's Callable<V>.
Callable<V>
Callable is a generic interface. Why? The type of the return value as the generic type. Callable is too a functional interface andcall()
is the only method, a no-argument method that throws Exception and returns generic type value.
The implementing Callable is very similar to Runnable:
x
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();
}
See here, thecall
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. Let's talk about why.
As you can see, there is no controlled behavior of creating and running the Threads (Runnable or Callable too). We may want to control the number of threads running at a time as each of them associated with OS's threads. The number of Threads we run should be lesser than the number of available CPU cores. All together, Java solves it by ExecutorService interface.
I think it's really a lot for today. Maybe in Part II, we will discuss Executors and different types of ExecutorService, and the Future<V> interface.
Opinions expressed by DZone contributors are their own.
Comments