DEV Community

Jane for Mastering Backend

Posted on • Originally published at masteringbackend.com on

Mastering Java Multithreading Thread Pools, Callable, Future, and Concurrency Utilities

Mastering Java Multithreading Thread Pools, Callable, Future, and Concurrency Utilities

title

Java multithreading becomes more powerful and efficient when you start using thread pools, Callable, Future, and built-in concurrency utilities. These features help manage multiple threads better, especially in large applications.

What is a Thread Pool?

A Thread Pool is a group of pre-created threads that can be reused to perform tasks. It avoids the overhead of creating a new thread every time and improves performance.

https://images.app.goo.gl/1CkeahkMgjNLhFh88

A thread pool is a group of worker threads managed by the Java runtime that are reused to execute multiple tasks, improving performance and resource management.

Using ExecutorService to Run Multiple Tasks

package ayshriv; 

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

public class Masteringbackend { 
    public static void main(String[] args) { 
        ExecutorService executor = Executors.newFixedThreadPool(2); 

        executor.submit(() -> System.out.println("Task 1 running")); 
        executor.submit(() -> System.out.println("Task 2 running")); 
        executor.submit(() -> System.out.println("Task 3 running")); 

        executor.shutdown(); 
  } 
}
Enter fullscreen mode Exit fullscreen mode

Output:

Task 1 running 
Task 2 running 
Task 3 running
Enter fullscreen mode Exit fullscreen mode

(Note: Order may vary)

In this above code, we created a thread pool with 2 threads using Executors.newFixedThreadPool(2). We submitted 3 tasks, and the thread pool handles them efficiently using available threads. The third task waits if both threads are busy.

What is Callable and Future in Java?

Callable is like Runnable but returns a result.

Future is used to get the result of a Callable after it’s done.

Callable is like a Runnable but can return a result or throw an exception. Future is used to retrieve the result of the Callable once it's done.

Using Callable and Future

package ayshriv; 

import java.util.concurrent.*; 

public class MasteringBackend { 
    public static void main(String[] args) throws Exception { 
        ExecutorService executor = Executors.newSingleThreadExecutor(); 

        Callable<String> task = () -> { 
            Thread.sleep(1000); 
            return "Result from Callable"; 
         }; 

         Future<String> future = executor.submit(task); 

         System.out.println("Doing other work..."); 
         String result = future.get(); // Waits for Callable to finish 
         System.out.println("Callable Result: " + result); 

         executor.shutdown(); 
    } 
 }
Enter fullscreen mode Exit fullscreen mode

Output:

Doing other work... 
Callable Result: Result from Callable
Enter fullscreen mode Exit fullscreen mode

In this above code, we used Callable to return a string result after a delay. While the callable runs in the background, the main thread does other work. The future.get() method waits and fetches the result once the task is complete.

Using Scheduled Executor Service

You can schedule tasks to run after a delay or repeatedly.

Scheduled Executor Service with Delay

package ayshriv; 

import java.util.concurrent.*; 

public class MasteringBackend { 
     public static void main(String[] args) throws InterruptedException { 
         ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); 

         Runnable task = () -> System.out.println("Task executed after delay"); 

         scheduler.schedule(task, 2, TimeUnit.SECONDS); 

         scheduler.shutdown(); 
  } 
}
Enter fullscreen mode Exit fullscreen mode

Output:

Task executed after delay
Enter fullscreen mode Exit fullscreen mode

(Appears after approximately 2 seconds)

In this above code, we used ScheduledExecutorService to schedule a task after 2 seconds. This is useful for reminders, retries, or timeout actions in real-time applications.

Using CountDownLatch

CountDownLatch is a utility that waits until all required tasks are finished before moving forward.

Waiting for 3 Threads to Finish

package ayshriv; 

import java.util.concurrent.CountDownLatch; 

public class MasteringBackend { 
    public static void main(String[] args) throws InterruptedException { 
        CountDownLatch latch = new CountDownLatch(3); 

        Runnable task = () -> { 
            System.out.println(Thread.currentThread().getName() + " completed"); 
            latch.countDown(); 
        }; 

        new Thread(task).start(); 
        new Thread(task).start(); 
        new Thread(task).start(); 

        latch.await(); // Wait for all 3 threads 
        System.out.println("All tasks finished. Proceeding..."); 
   } 
}
Enter fullscreen mode Exit fullscreen mode

Output:

Thread-0 completed 
Thread-1 completed 
Thread-2 completed 
All tasks finished. Proceeding...
Enter fullscreen mode Exit fullscreen mode

In this above code, we used CountDownLatch(3) to wait until 3 threads complete their tasks. Only after all threads call countDown(), the main thread resumes and prints the final message.

Using CyclicBarrier

CyclicBarrier allows multiple threads to wait for each other to reach a common barrier point.

A synchronization tool that allows a group of threads to wait for each other to reach a common barrier point before continuing execution.

Threads Sync at Barrier

package ayshriv; 

import java.util.concurrent.*; 

public class MasteringBackend { 
    public static void main(String[] args) { 
         CyclicBarrier barrier = new CyclicBarrier(3, () -> 
                 System.out.println("All threads reached barrier. Running final task.")); 

          Runnable task = () -> { 
              System.out.println(Thread.currentThread().getName() + " is waiting at barrier"); 
              try { 
                  barrier.await(); // Wait for all threads 
               } catch (Exception e) { 
                   e.printStackTrace(); 
               } 
          }; 

          new Thread(task).start(); 
          new Thread(task).start(); 
          new Thread(task).start(); 
  } 
}
Enter fullscreen mode Exit fullscreen mode

Output:

Thread-0 is waiting at barrier 
Thread-1 is waiting at barrier 
Thread-2 is waiting at barrier 
All threads reached barrier. Running final task.
Enter fullscreen mode Exit fullscreen mode

In this above code, all 3 threads wait at the barrier. When all threads reach the barrier, the barrier action runs. This is useful in parallel processing where you want all threads to finish a phase together before moving to the next.

Have a great one!!!

Author: Ayush Shrivastava


Thank you for being a part of the community

Before you go:

Whenever you’re ready

There are 4 ways we can help you become a great backend engineer:

  • The MB Platform: Join thousands of backend engineers learning backend engineering. Build real-world backend projects, learn from expert-vetted courses and roadmaps, track your learnings and set schedules, and solve backend engineering tasks, exercises, and challenges.
  • The MB Academy: The “MB Academy” is a 6-month intensive Advanced Backend Engineering Boot Camp to produce great backend engineers.
  • Join Backend Weekly: If you like posts like this, you will absolutely enjoy our exclusive weekly newsletter, sharing exclusive backend engineering resources to help you become a great Backend Engineer.
  • Get Backend Jobs: Find over 2,000+ Tailored International Remote Backend Jobs or Reach 50,000+ backend engineers on the #1 Backend Engineering Job Board.

Originally published at https://masteringbackend.com on August 3, 2025.


Top comments (0)