With the introduction of java.util.concurrent apis in Java 5, handling asynchronous and concurrent tasks in Java has become easier and less error prone. It is also the best and recommended approach to deal with threads and thread pools.
Some of the important interfaces and classes among them are shown below. The most important one to know about is the ExecutorService Interface.

Once we get an instance of the ExecutorService we can submit the tasks (Runnable or Callable) using its submit methods. But ExecutorService
is an Interface. To get an instance of ExecutorService use one of the factory methods from the utility class Executors
. It has various factory methods(Creational Pattern) which can return instances of ExecutorService
. Some of them are shown below.

Creating a Fixed ThreadPool ExecutorService.
One of the advantages of creating a ThreadPool is it minimizes the overhead due to Thread creation. Thread objects use a significant amount of memory. Allocating and deallocation of large number of Thread objects cause a significant amount of overhead in memory management of the JVM. A common way to create an ExecutorService instance is to create a fixed Thread pool Executor Service.
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
It has a fixed number of worker threads. If one Thread is terminated somehow another thread will replace it immediately. In below code we are creating a new fixed Thread Pool ExecutorService instance of 3 threads using the below line of code: ExecutorService service = Executors.newFixedThreadPool(3)
; and submitting 3 Runnable tasks to it.
public class ExecutorServiceDemo {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(3);
try {
service.submit(() -> task("task1"));
service.submit(() -> task("task2"));
service.submit(() -> task("task3"));
}finally{
service.shutdown();
}
}
public static void task(String taskName){
try{
System.out.printf("%s started %s %n" , Thread.currentThread().getName(), taskName);
Thread.sleep(1000l);
System.out.printf("%s completed %s %n" , Thread.currentThread().getName(), taskName);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
If we submit more tasks to it as shown below, task4 will wait in queue till any one of the first 3 threads finishes its task and becomes available.
ExecutorService service = Executors.newFixedThreadPool(3);
try {
service.submit(() -> task("task1"));
service.submit(() -> task("task2"));
service.submit(() -> task("task3"));
service.submit(() -> task("task4")); //wait in queue
}finally{
service.shutdown();
}
Note: Calling shutdown()
method will shutdown the ExecutorService
gracefully. It will wait till all the submitted tasks are completed successfully and no new tasks will be accepted that that time. Once the shutdown is fully completed ExecutorService
moves to terminated
state. Only after that the jvm will terminate. There is another method called shutdownNow()
which will try to terminate the ExecutorService
immediately interrupting all the submitted tasks.
There are also other ExecutorService instances that we can create using the factory methods of Executors class. Two of them are provided below.
- newSingleThreadExecutor creates a thread pool Executor Service with only one worker thread.
ExecutorService service = Executors.newSingleThreadExecutor();
- CachedThreadPool creates a new Thread every time when needed but uses previously created threads if they are available. It can create upto Integer.MAX_VALUE number of threads. So it is not encouraged to use this for long running threads as it may create large number of threads and may eat up all the CPU. However, these are good for short lived threads.
ExecutorService service = Executors.newCachedThreadPool();