Java
Multithreading
Thread Management
Concurrency
Java Programming

wait until all threads finish their work in java

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In multithreaded applications, situations often arise where you need to wait for a group of threads to finish their execution before proceeding with subsequent operations. This is a common pattern when the main thread spawns several worker threads and needs to aggregate their results or simply wait until all tasks are complete. Java, with its robust concurrency utilities, offers several ways to achieve this synchronization. This article delves into various techniques available in Java for waiting until all threads finish their work.

Understanding Threads in Java

Java threads are a part of the java.lang package and represent a lightweight, concurrent unit of execution within a process. Every Java program has at least one thread, known as the main thread. Additional threads can be created either by extending the Thread class or by implementing the Runnable interface.

Techniques to Wait Until All Threads Finish

Java provides multiple mechanisms to handle scenarios where you want to wait for threads to complete:

  1. Using the Thread.join() method
  2. Using ExecutorService and Future
  3. Using CountDownLatch
  4. Using CompletableFuture

1. Using the Thread.join() Method

The join() method in Java allows one thread to wait for the completion of another. This method can be used when you have precise control over individual threads.

Example:

java
1public class ThreadJoinExample {
2    public static void main(String[] args) throws InterruptedException {
3        Thread t1 = new Thread(() -> {
4            System.out.println("Thread 1 is running.");
5        });
6        
7        Thread t2 = new Thread(() -> {
8            System.out.println("Thread 2 is running.");
9        });
10        
11        t1.start();
12        t2.start();
13        
14        // Main thread waits for both threads to finish.
15        t1.join();
16        t2.join();
17        
18        System.out.println("All threads have finished execution.");
19    }
20}

2. Using ExecutorService and Future

Java's concurrency framework provides the ExecutorService interface which manages and controls thread execution. Using invokeAll() method, you can submit multiple tasks and wait for their completion.

Example:

java
1import java.util.Arrays;
2import java.util.List;
3import java.util.concurrent.Callable;
4import java.util.concurrent.ExecutionException;
5import java.util.concurrent.ExecutorService;
6import java.util.concurrent.Executors;
7import java.util.concurrent.Future;
8
9public class ExecutorServiceExample {
10    public static void main(String[] args) throws InterruptedException, ExecutionException {
11        ExecutorService executor = Executors.newFixedThreadPool(2);
12
13        List<Callable<String>> tasks = Arrays.asList(
14            () -> "Task 1",
15            () -> "Task 2"
16        );
17
18        List<Future<String>> futures = executor.invokeAll(tasks);
19
20        for (Future<String> future : futures) {
21            // Waits for each task to complete and retrieves the result
22            System.out.println("Result: " + future.get());
23        }
24
25        executor.shutdown();
26    }
27}

3. Using CountDownLatch

CountDownLatch is a synchronization aid that allows one or more threads to wait until a set of operations being performed by other threads is complete.

Example:

java
1import java.util.concurrent.CountDownLatch;
2
3public class CountDownLatchExample {
4    public static void main(String[] args) throws InterruptedException {
5        CountDownLatch latch = new CountDownLatch(2);
6
7        Thread t1 = new Thread(() -> {
8            System.out.println("Thread 1 is doing work.");
9            latch.countDown();
10        });
11
12        Thread t2 = new Thread(() -> {
13            System.out.println("Thread 2 is doing work.");
14            latch.countDown();
15        });
16
17        t1.start();
18        t2.start();
19
20        // Wait for the count to reach zero
21        latch.await();
22        System.out.println("All threads have finished execution.");
23    }
24}

4. Using CompletableFuture

Introduced in Java 8, CompletableFuture is a versatile utility that allows you to wait for multiple futures to complete.

Example:

java
1import java.util.concurrent.CompletableFuture;
2import java.util.concurrent.ExecutionException;
3
4public class CompletableFutureExample {
5    public static void main(String[] args) throws ExecutionException, InterruptedException {
6        CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
7            System.out.println("Async task 1 is running.");
8        });
9
10        CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
11            System.out.println("Async task 2 is running.");
12        });
13
14        // Wait for all futures to complete
15        CompletableFuture.allOf(future1, future2).join();
16        System.out.println("All tasks have completed.");
17    }
18}

Comparing Techniques

Below is a comparison table that summarizes the key points of each technique:

TechniqueDescriptionProsCons
Thread.join()Waits for threads to finish individually.Simple and direct.Not suitable for large numbers of threads.
ExecutorServiceManages tasks with thread pools and awaits completion.Scales well with thread pools.Requires more setup and teardown.
CountDownLatchSynchronizes by counting down to zero.Useful for multiple dependencies.One-time use; cannot be reset.
CompletableFutureAllows chaining and combines futures.Highly versatile with functional programming.More complex and syntax-heavy for beginners.

Conclusion

Java's concurrency utilities provide powerful and versatile methods to handle the synchronization of threads. Choosing the right one depends on your specific use case, quantity of threads, and desired complexity of implementation. Understanding each tool's strengths and limitations will enable you to write more efficient and maintainable multi-threaded Java applications.


Course illustration
Course illustration

All Rights Reserved.