Backend Development 12 min read

Four Ways to Determine When a Java ThreadPool Has Completed All Tasks

This article explains four practical techniques—using isTerminated, comparing task counts, CountDownLatch, and CyclicBarrier—to reliably detect when a Java ThreadPoolExecutor has finished executing all submitted tasks, including code examples, advantages, disadvantages, and a summary of each method.

IT Services Circle
IT Services Circle
IT Services Circle
Four Ways to Determine When a Java ThreadPool Has Completed All Tasks

In many scenarios we need to wait until all tasks submitted to a ThreadPoolExecutor finish before proceeding; unlike a simple Thread where join() suffices, checking a thread pool’s completion is more involved.

The article presents four methods to determine whether a thread pool has completed its work:

Using isTerminated()

Using getCompletedTaskCount() and getTaskCount()

Using a CountDownLatch

Using a CyclicBarrier

Method 1: isTerminated

Call shutdown() on the pool and then loop while !threadPool.isTerminated() . This requires closing the pool, which may be undesirable in some cases.

public static void main(String[] args) {
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 20, 0, TimeUnit.SECONDS, new LinkedBlockingDeque<>(1024));
    addTask(threadPool);
    isCompleted(threadPool);
    System.out.println("ThreadPool tasks completed!");
}

private static void isCompleted(ThreadPoolExecutor threadPool) {
    threadPool.shutdown();
    while (!threadPool.isTerminated()) {
        // wait
    }
}

Pros: Simple logic.

Cons: Must shut down the pool; not suitable if the pool should stay alive.

Method 2: getCompletedTaskCount

Continuously compare the total planned task count with the completed task count; when they match, all tasks are done. This does not require shutting down the pool.

private static void isCompletedByTaskCount(ThreadPoolExecutor threadPool) {
    while (threadPool.getTaskCount() != threadPool.getCompletedTaskCount()) {
        // busy‑wait
    }
}

Pros: No need to close the pool.

Cons: Both counts are approximate because the pool state can change during the check.

Method 3: CountDownLatch

Create a CountDownLatch with a count equal to the number of tasks. Each task calls countDown() when finished, and the main thread waits on await() until the count reaches zero.

public static void main(String[] args) throws InterruptedException {
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 20, 0, TimeUnit.SECONDS, new LinkedBlockingDeque<>(1024));
    int taskCount = 5;
    CountDownLatch latch = new CountDownLatch(taskCount);
    for (int i = 0; i < taskCount; i++) {
        final int idx = i;
        threadPool.submit(() -> {
            try {
                int sleep = new Random().nextInt(5);
                TimeUnit.SECONDS.sleep(sleep);
            } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println(String.format("Task %d completed", idx));
            latch.countDown();
        });
    }
    latch.await();
    System.out.println("ThreadPool tasks completed!");
}

Pros: Elegant, does not require shutting down the pool, and is widely used.

Cons: The latch can be used only once; a new latch is needed for another round.

Method 4: CyclicBarrier

Similar to CountDownLatch but reusable. A CyclicBarrier is created with the task count and an optional barrier action that runs when the count reaches zero. Each task calls await() after finishing.

public static void main(String[] args) throws InterruptedException, BrokenBarrierException {
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 20, 0, TimeUnit.SECONDS, new LinkedBlockingDeque<>(1024));
    int taskCount = 5;
    CyclicBarrier barrier = new CyclicBarrier(taskCount, () -> {
        System.out.println("All tasks have finished!");
    });
    for (int i = 0; i < taskCount; i++) {
        final int idx = i;
        threadPool.submit(() -> {
            try {
                int sleep = new Random().nextInt(5);
                TimeUnit.SECONDS.sleep(sleep);
                System.out.println(String.format("Task %d completed", idx));
                barrier.await();
            } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); }
        });
    }
}

Pros: Reusable after calling reset() .

Cons: More complex to use and understand compared with CountDownLatch .

Summary

The four approaches each have trade‑offs: isTerminated requires pool shutdown; getCompletedTaskCount gives approximate results; CountDownLatch is simple and common but single‑use; CyclicBarrier is reusable but more intricate. Choose the method that best fits your project's lifecycle and complexity requirements.

JavaconcurrencythreadpoolThreadPoolExecutorCountDownLatchCyclicBarrierisTerminated
IT Services Circle
Written by

IT Services Circle

Delivering cutting-edge internet insights and practical learning resources. We're a passionate and principled IT media platform.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.