Java Thread join(): Wait for Thread Completion with Timeout and Examples

0

Thread Join in Java

Multithreading allows several tasks to make progress concurrently, but sometimes one thread must wait for another thread to finish before it can safely continue. Java provides the join() method for this purpose.

Think of two developers working on a project. One developer generates a configuration file while another needs that file before starting the deployment. The deployment task should not simply guess when the first task is finished. It should explicitly wait for completion. In Java, join() provides exactly this kind of coordination.

Important: join() makes the thread that calls it wait for the target thread to terminate. It does not pause or stop the target thread.

What Is join()?

The join() method belongs to the Thread class. When one thread calls join() on another thread, the calling thread waits until the target thread terminates.

Thread worker = new Thread(() -> {

    System.out.println("Worker is running");

});

worker.start();

try {

    worker.join();

}
catch (InterruptedException e) {

    Thread.currentThread().interrupt();
}

System.out.println("Worker has finished");

Here, the main thread starts the worker and then calls worker.join(). The main thread waits until the worker has completed its execution.

Remember: The thread that calls join() waits. The thread on which join() is called continues its own execution until it terminates.

Why Do We Need join()?

Without explicit coordination, threads can finish in an unpredictable order.

Thread worker = new Thread(() -> {

    System.out.println("Generating report");

});

worker.start();

System.out.println("Sending report");

There is no guarantee that "Generating report" will finish before "Sending report" is printed. The main thread may continue immediately after starting the worker.

If sending the report requires the generation task to finish first, join() can establish that dependency.

Thread worker = new Thread(() -> {

    System.out.println("Generating report");

});

worker.start();

try {

    worker.join();

}
catch (InterruptedException e) {

    Thread.currentThread().interrupt();
}

System.out.println("Sending report");

Now the main thread does not reach the sending operation until the worker thread has terminated.

Basic join() Syntax

thread.join();

The no-argument form waits until the target thread terminates.

Because the waiting thread can itself be interrupted, join() declares InterruptedException.

join() with a Timeout

Java also provides a timed version of join(). This allows the calling thread to wait for a maximum amount of time.

try {

    worker.join(2000);

}
catch (InterruptedException e) {

    Thread.currentThread().interrupt();
}

The value 2000 represents approximately two seconds. If the worker finishes earlier, the waiting thread can continue earlier. If the worker is still running when the timeout expires, the waiting thread can continue without waiting indefinitely.

join() with Milliseconds and Nanoseconds

Java also provides an overload that accepts milliseconds and additional nanoseconds.

try {

    worker.join(2000, 500000);

}
catch (InterruptedException e) {

    Thread.currentThread().interrupt();
}

As with sleep(), these values should not be interpreted as guarantees of exact scheduling precision. Actual execution depends on the JVM and operating system.

Which Thread Actually Waits?

This is a common interview question.

Thread worker = new Thread(() -> {

    System.out.println("Worker running");

});

worker.start();

worker.join();

The current thread waits. In this example, if the code is running inside main(), the main thread waits for the worker.

The worker does not wait for itself. It continues executing until its work finishes.

Interview tip: The expression worker.join() means "the current thread, please wait for worker to finish."

Example: Waiting for Multiple Threads

A common real-world situation involves several independent tasks. The main thread may need to wait until all of them are complete before producing the final result.

Thread first = new Thread(() -> {

    System.out.println("First task started");

    try {
        Thread.sleep(1000);
    }
    catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }

    System.out.println("First task completed");
});

Thread second = new Thread(() -> {

    System.out.println("Second task started");

    try {
        Thread.sleep(1500);
    }
    catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }

    System.out.println("Second task completed");
});

first.start();
second.start();

try {

    first.join();
    second.join();

}
catch (InterruptedException e) {

    Thread.currentThread().interrupt();
}

System.out.println("All tasks completed");

The two worker threads can execute concurrently. The main thread waits for both of them before printing the final message.

join() Does Not Make Threads Sequential

A subtle but important point is that using join() does not automatically make all threads execute sequentially.

first.start();
second.start();

try {

    first.join();
    second.join();

}
catch (InterruptedException e) {

    Thread.currentThread().interrupt();
}

Both threads are started before the main thread waits. Therefore, the two tasks can still run concurrently. The joins only make the current thread wait until both target threads have completed.

This is very different from:

first.start();

try {
    first.join();
}
catch (InterruptedException e) {
    Thread.currentThread().interrupt();
}

second.start();

In the second example, the second thread is not even started until the first thread has finished, so the two tasks cannot overlap in the same way.

Key distinction: Start independent threads first when you want concurrent work. Use join() afterward when you need to wait for their completion.

join() and Thread Lifecycle

When a thread calls join() on another thread, the calling thread enters the WAITING state for the no-argument version until the target thread terminates.

For a timed join, the calling thread can enter TIMED_WAITING.

Operation Calling Thread Target Thread
join() Waits indefinitely Continues execution
join(timeout) Waits for at most the specified duration Continues execution
Target terminates Can continue Enters TERMINATED

join() and InterruptedException

Because the calling thread is waiting, another thread can interrupt it while it is inside join().

try {

    worker.join();

}
catch (InterruptedException e) {

    System.out.println(
        "Waiting thread was interrupted"
    );

    Thread.currentThread().interrupt();
}

The important detail is that the interruption affects the thread that is waiting, not the worker thread being joined.

Example: Main Thread Waiting for Worker

public class JoinDemo {

    public static void main(String[] args) {

        Thread worker = new Thread(() -> {

            System.out.println(
                "Worker started"
            );

            try {
                Thread.sleep(2000);
            }
            catch (InterruptedException e) {

                Thread.currentThread().interrupt();
            }

            System.out.println(
                "Worker completed"
            );
        });

        worker.start();

        System.out.println(
            "Main is waiting"
        );

        try {

            worker.join();

        }
        catch (InterruptedException e) {

            Thread.currentThread().interrupt();
        }

        System.out.println(
            "Main continues"
        );
    }
}

The worker performs its task independently. The main thread then waits at join(). Once the worker terminates, the main thread continues.

join() vs sleep()

join() sleep()
Waits for another thread to finish. Pauses the current thread for a requested duration.
Represents thread completion coordination. Represents a timed pause.
No-argument join can wait indefinitely. Requires a requested duration.
Can be interrupted while waiting. Can be interrupted while sleeping.
Target thread continues executing. The current thread pauses its own execution.

join() vs wait()

join() wait()
Defined in Thread. Defined in Object.
Waits for a specific thread to terminate. Waits for notification or another condition.
Designed for thread completion coordination. Designed for monitor-based coordination.
Does not require manually acquiring an object's monitor. Must be called while owning the object's monitor.

Calling join() Before start()

A useful edge case is calling join() on a Thread that has not been started.

Thread worker = new Thread(() -> {

    System.out.println("Working");

});

try {

    worker.join();

}
catch (InterruptedException e) {

    Thread.currentThread().interrupt();
}

There is no running target thread to wait for, so the join can return without providing meaningful synchronization. In normal application logic, start the target thread before waiting for it.

Calling join() After Termination

If the target thread has already terminated, calling join() returns immediately because there is nothing left to wait for.

Thread worker = new Thread(() -> {

    System.out.println("Work completed");

});

worker.start();

try {

    worker.join();

    // The worker is already terminated here.

    worker.join();

}
catch (InterruptedException e) {

    Thread.currentThread().interrupt();
}

The second join does not cause another delay because the target thread has already completed.

Common Beginner Mistakes

  • Thinking join() stops the target thread.
  • Forgetting that the calling thread is the one that waits.
  • Starting one thread, immediately joining it, and then starting another when concurrent execution was intended.
  • Ignoring InterruptedException.
  • Confusing join() with sleep().
  • Assuming join(timeout) guarantees that the target thread has finished when the method returns.

Best Practices

  • Use join() when a thread genuinely depends on another thread's completion.
  • Start independent tasks before joining them when you want them to execute concurrently.
  • Handle InterruptedException according to the application's interruption policy.
  • Use timed join() when indefinite waiting is undesirable.
  • For complex task coordination, consider higher-level concurrency utilities instead of manually coordinating many Thread objects.

Interview Insights

Question: What does join() do in Java?

It makes the current thread wait until the target thread terminates.

Question: Which thread waits when worker.join() is called?

The thread that calls worker.join() waits. The worker thread continues its execution.

Question: Does join() stop a thread?

No. It only causes the calling thread to wait for the target thread to terminate.

Question: What happens when join(timeout) returns because the timeout expires?

The calling thread continues even if the target thread is still running. Therefore, a timed join does not guarantee that the target has completed.

Question: Does join() make multiple threads execute sequentially?

Not necessarily. If multiple threads are started first and then joined, they can execute concurrently while the calling thread waits for their completion.

Quick Revision

Concept Key Point
join() Makes the calling thread wait for the target thread to terminate.
join(timeout) Waits for at most the specified duration.
Waiting thread The thread that calls join() enters a waiting state.
Target thread Continues execution until it terminates.
InterruptedException Can occur when the waiting thread is interrupted.
Concurrent tasks Start independent threads first, then join them when completion is required.
After termination Joining an already terminated thread returns immediately.

The power of join() lies in its simplicity: it gives one thread a reliable way to say, "I cannot continue until this other thread has finished." It does not kill, pause, or control the target thread; it simply coordinates completion. Once this distinction is clear, the next topic—Daemon Threads—will show how Java treats background threads differently when the JVM is ready to shut down.

Post a Comment

0Comments
Post a Comment (0)