Explain Codes LogoExplain Codes Logo

How to know if other threads have finished?

java
concurrency
threading
best-practices
Anton ShumikhinbyAnton Shumikhin·Jan 22, 2025
TLDR

To check if other threads have finished, use the join() method for each thread. This will cause the current thread to pause until the targeted threads terminate.

Thread t1 = new Thread(() -> { /* do some heavy lifting */ }); Thread t2 = new Thread(() -> { /* solve world hunger */ }); t1.start(); t2.start(); t1.join(); // We chill here until t1 has done its heavy lifting t2.join(); // Hold up right here until t2 has solved world hunger // Continue life knowing the heavy lifting is done and world hunger is solved

join() ensures that t1 and t2 finish before the main thread resumes.

For heavy-duty, enterprise-grade stuff, pair ExecutorService with Futures. Futures give you the status and results of the task upon completion. Like future telling, but less mystical and more predictable!

ExecutorService executor = Executors.newFixedThreadPool(4); // Hey 4 threads, do my biddings Callable<String> task = () -> {/* secret to achieving world peace */}; Future<String> future = executor.submit(task); // Tell me the secret when you find it executor.shutdown(); executor.awaitTermination(1, TimeUnit.HOURS); // Wait for everyone to come back with the secrets. Take an hour off, go grab some coffee!

Concurrency, the Java way

Leverage concurrency tools

Java's java.util.concurrent package provides advanced tools for handling threads.

  • CountDownLatch: A synchronization aid that allows one or more threads to wait until a set of operations in other threads completes. Like waiting on your friends before you start binging your favorite series.
CountDownLatch latch = new CountDownLatch(2); // Waiting for 2 friends new Thread(() -> { /* watch an episode */ latch.countDown(); }).start(); // One friend finished watching new Thread(() -> { /* watch an episode */ latch.countDown(); }).start(); // The other friend is done too latch.await(); // Waits until your friends finish watching
  • CyclicBarrier: Allows a group of threads to wait for each other to reach a common barrier point. Like a high-five among threads!

  • ExecutorService: Manages threads (think of it as your personal thread butler), submits Callable tasks, and has features like invokeAll() for massive synchronization.

Handle exceptions with finesse

Utilize setUncaughtExceptionHandler to trigger a callback when a thread abruptly stops due to an exception:

Thread thread = new Thread(() -> { throw new RuntimeException("Ouch that hurts!"); // Ooops a wild exception }); thread.setUncaughtExceptionHandler((t, e) -> System.out.println(t + " says: " + e)); // Gotcha exception! thread.start();

Go for Callable for return values

Use java.util.concurrent.Callable to fetch return values from threads. Thread by itself can't return anything!

ExecutorService executorService = Executors.newSingleThreadExecutor(); Future<Integer> future = executorService.submit(() -> "Usain Bolt"); // Thread task returning a value String fastestMan = future.get(); // Retrieve the fastest man on earth

Avoid threading "sins"

Deadlocks: Make sure locks are obtained and released orderly, and all locks are held during changes to shared resources.

Thread Leaks: Always shutdown threads after use to avoid wasting resources. It's like leaving the tap open!

Race Conditions: Use thread-safe classes and synchronization constructs to prevent shared data from getting a bad case of inconsistency.

Best practices for threading

Choose runnable over Thread

Instead of extending Thread directly, extend Runnable and use callbacks for handling thread status. This makes your code neat and modular like a Lego set.

public class MyTask implements Runnable { private final CompletableFuture<Void> doneSignal; MyTask(CompletableFuture<Void> doneSignal) { this.doneSignal = doneSignal; } @Override public void run() { // Your task code doneSignal.complete(null); } } CompletableFuture<Void> signal = new CompletableFuture<>(); new Thread(new MyTask(signal)).start(); signal.join(); // Waits for MyTask to complete