Skip to main content

Command Palette

Search for a command to run...

Mastering Java Concurrency: Thread pools, Future, CompletableFuture, ForkJoinPool & Scheduled Executors

Updated
5 min read
Mastering Java Concurrency: Thread pools, Future, CompletableFuture, ForkJoinPool & Scheduled Executors
N
Java & Spring Boot learner | Writing beginner-friendly technical articles | Exploring backend development

Modern applications require high performance and the ability to handle many tasks simultaneously. Java provides powerful tools for concurrent programming through the Executor Framework and advanced utilities like Thread Pools, Future, CompletableFuture, ForkJoinPool, and ScheduledThreadPoolExecutor.

In this article, we will deeply understand these concepts with examples so that developers and interview aspirants can confidently work with Java concurrency.

1. Why Multithreading Needs Thread Pools

Creating threads manually using the Thread class is not efficient in large-scale applications.

Problems with creating threads manually:

  • Creating a thread is expensive

  • Too many threads can exhaust system resources

  • Thread creation and destruction increases CPU overhead

  • Difficult to manage lifecycle

To solve this problem, Java introduced Thread Pools as part of the Executor Framework.

2. What is a Thread Pool?

A Thread Pool is a group of pre-created worker threads that execute submitted tasks.

Instead of creating new threads for each task, tasks are reused by available threads in the pool.

Benefits:

  • Better performance

  • Controlled resource usage

  • Improved thread management

  • Reduced thread creation overhead

3. Executor Framework

The Executor Framework simplifies thread management by separating:

Task submission from Task execution

Key Interfaces:

  • Executor

  • ExecutorService

  • ScheduledExecutorService

Common methods:

submit()
execute()
shutdown()
shutdownNow()

Example:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorExample{

     public static void main(String[] args){

        ExecutorService executor = Executors.newFixedThreadPool(3);

           for(int i=1; i<=5; i++){
 
                int task = i;

                executor.submit(()->{
                  
                   System.out.println("Executing task" + task + "by" +                          
                         
                                Thread.currentThread().getName());
 
                          });
               }

               executor.shutdown();
      }
}

Here, only 3 threads execute multiple tasks efficiently.

4. Types of Thread Pools

Java provides several thread pool implementation through the Executors class.

4.1 Fixed Thread Pool

Creates a fixed number of threads.

Executors.newFixedThreadPool(n)

Example:

ExecutorService executor = Executors.newFixedThreadPool(4);

Characteristics:

  • Fixed number of threads

  • Suitable for CPU intensive tasks

  • Tasks wait in queue if all threads are busy

4.2 Cached Thread Pool

Creates threads dynamically when needed.

Executors.newCachedThreadPool()

Characteristics:

  • Threads created as required

  • Idle threads reused

  • Good for short-lived asynchronous tasks

4.3 Single Thread Executor

Uses only one worker thread

Executors.newSingleThreadExecutor()

Characteristics:

  • Executes tasks sequentially

  • Guarantees order of execution

5. Callable Interface

In Java, the Runnable interface cannot return results or throw checked exceptions.

To overcome this limitation, Java introduced Callable.

Callable<V>

Characteristics:

  • Returns a result

  • Can throw checked exceptions

  • Used with ExecutorService

import java.util.concurrent.Callable;

class SumTask implements Callable<Integer>{

       public Integer call(){
            int sum = 0;

         for(int i=1; i<=10; i++){
            sum += i;
          }
         
           return sum;
    }
}

6. Future Interface

When a task is submitted using Callable, the result is represented using a Future object.

Future represents the result of an asynchronous computation.

Common methods:

get()
isDone()
cancel()
isCancelled()

Example:

import java.util.concurrent.*;

public class FutureExample{

    public static void main(String[] args) throws Exception {
         
       ExecutorService executor = Executors.newSingleThreadExecutor();
      
       Callable<Integer> task = () -> 10 + 20;
    
       Future<Integer> future = executor.submit(task);

        System.out.println("Result: " + future.get());

        executor.shutdown();
    }
}

Here the get() method waits until the computation is finished.

7. CompletableFuture

CompletableFuture is a powerful enhancement over Future.

Problems with Future:

  • Blocking

  • Cannot chain multiple tasks

  • Limited asynchronous control

CompletableFuture solves these limitations by supporting asynchronous programming and chaining tasks.

Example:

import java.util.concurrent.CompletableFuture;
 
     public class CompletableFutureExample{

        public static void main(String[] args){

           CompletableFuture.supplyAsync(() -> {
                    
                  return "Hello";

             }).thenApply(result -> result + "Java")
           
               .thenAccept(System.out::println);
    }
}

Output:

Hello Java

Features:

  • Non-blocking programming

  • Task chaining

  • Parallel execution

  • Exceptional handling support

8. ForkJoin Framework

The ForkJoin Framework is designed for parallel processing of large tasks.

It works using the divide and conquer strategy.

Steps:

  1. Task is split into smaller tasks (fork)

  2. Subtasks are processed in parallel

  3. Results are combined(join)

It uses a special thread pool called ForkJoinPool.

Example:

import java.util.concurrent.*;

class SumTask extends RecursiveTask<Integer>{

        int start, end;

        SumTask(int start, int end){
            this.start = start;
            this.end = end;
        }

      protected Integer compute(){

          if(end - start <=10){
            int sum = 0;

              for(int i=start; i<=end; i++){
                  sum += i;
               }
            return sum;
           }
          
           int mid = (start + end)/2;

          SumTask left = new SumTask(start, mid);

          SumTask right = new SumTask(mid+1, end);

           left.fork();

           return right.compute() + left.join();
      }
}

Used in:

  • Parallel algorithms

  • Big data processing

  • High performance systems

9. ScheduledThreadPoolExecutor

Sometimes tasks must run after a delay or periodically.

For this purpose Java provides ScheduledThreadPoolExecutor.

Example:

import java.util.concurrent.*;

public class ScheduledExample{

      public static void main(String[] args){

         ScheduledExecutorService scheduler = 
                   
                 Executors.newScheduledThreadPool(2);

           scheduler.schedule( () -> System.out.println("Task executed  
                                        after delay"),3,TimeUnit.SECONDS);
     }
}

Periodic Task Example

scheduler.scheduleAtFixedRate(() -> {

           System.out.println("Running every 2 seconds");
   
       }, 0, 2, TimeUnit.SECONDS);

Use cases:

  • Scheduled jobs

  • Monitoring systems

  • Periodic background tasks

10. Summary

Java provides powerful tools to manage concurrency efficiently.

Key concepts covered:

  • Thread Pools

  • Executor Framework

  • Fixed, Cached and Single Thread Pools

  • Callable Interface

  • Future Interface

  • CompletableFuture

  • ForkJoin Framework

  • ScheduledThreadPoolExecutor

Understanding these tools allows developers to build high-performance, scalable, and responsive applications.

Conclusion

Concurrency is one of the most important skills for a professional Java developer. Instead of manually managing threads, Java's Executor Framework and advanced utilities provide structured and efficient ways to build scalable applications.

More from this blog

CoreJava

19 posts

I have written and published a comprehensive blog series titled "CoreJava" on Hashnode, based on my learning journey from basics to advanced. The series includes topics like OOP, Collections, Exception Handling, Multithreading, and Java Streams, explained with clear examples and practical insights to help learners build a strong foundation in java.