Unlock Your Python Backend Career: Build 30 Projects in 30 Days. Join now for just $54

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

by Ayush Shrivastava

.

Updated Mon Aug 04 2025

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

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

Mastering (9) (1).pngJava 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();
    }
}

Output:

Task 1 running
Task 2 running
Task 3 running

(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();
    }
}

Output:

Doing other work...
Callable Result: Result from Callable

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 ScheduledExecutorService

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

ScheduledExecutorService 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();
    }
}

Output:

Task executed after delay

(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...");
    }
}

Output:

Thread-0 completed
Thread-1 completed
Thread-2 completed
All tasks finished. Proceeding...

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();
    }
}

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.

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.

Course image
Become a Java Backend Engineeer today

All-in-one Java course for learning backend engineering with Java. This comprehensive course is designed for Java developers seeking proficiency in Java.

Start Learning Now

Whenever you're ready

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

The MB Platform

Join 1000+ 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 BootCamp to produce great backend engineers.

Join Backend Weekly

If you like post 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

Backend Tips, Every week

Backend Tips, Every week