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 Engineer today

All-in-one Java course for learning backend engineering. Designed for Java developers seeking proficiency.

Start Learning Now