skip to content

sleep, join, yield

sleep pauses without releasing any lock, join waits for another thread to finish, and yield is only a hint the scheduler may ignore. The sleep-holds-the-lock detail is the one interviewers most often probe.

part ofJavaoverview, primer and where to startread it →
on this pageshow

questions

5

What does Thread.join() do, and when would you use it?

level: juniorimportance: must knowfreq 72%

answer

  1. join = wait for that thread to die
  2. caller blocks in WAITING / TIMED_WAITING
  3. timed join may return early → check isAlive()
  4. join establishes happens-before (results visible)
  5. throws InterruptedException; join(0) = wait forever

basics

~10 s

Calling t.join() makes the current thread wait until thread t finishes before continuing. You use it to make sure a worker thread's work is done before reading its results.

solid answer

~40 s

Thread.join() lets one thread wait for another to terminate. If thread A calls b.join(), A blocks (in WAITING, or TIMED_WAITING if a timeout is given) until thread B's run() completes — by returning, throwing, or being terminated. join() is built on wait/notify internally: the JVM notifies waiters when the thread dies. The overloads join(millis) and join(millis, nanos) wait at most that long, then return regardless of whether the thread finished — so after a timed join you should check isAlive() to know if it actually completed. join() throws InterruptedException. Typical use: a main thread spawns workers, then joins each so it can safely read their results, knowing the workers' writes happen-before the join returns (memory visibility is guaranteed). Joining a thread that has already finished returns immediately.

code

java · 11 lines
java
Thread worker = new Thread(() -> {
    // compute something
});
worker.start();

worker.join(2000);            // wait up to 2s
if (worker.isAlive()) {
    // timed out: worker still running
} else {
    // worker finished; its results are safely visible here
}

go deeper

for a junior

Knows t.join() makes the current thread wait until t finishes, used to sequence work.

for a middle

Explains WAITING vs TIMED_WAITING, the timeout overloads + isAlive() check, and that join throws InterruptedException.

for a senior

Articulates the happens-before/memory-visibility guarantee, that join is built on wait/notify, the join(0) gotcha, and that interrupting the joiner doesn't stop the target.

for a principal

Compares raw join to ExecutorService/Future/CompletableFuture/structured concurrency, reasons about cancellation, error propagation, and scaling coordination beyond a handful of threads.

## The problem join solves When you start a thread with `t.start()`, it runs **concurrently** — your code keeps going while the new thread does its work. Often you need to **wait for that thread to finish** before moving on (e.g., to combine its results). `Thread.join()` is exactly that: "block me until that other thread has ended." ## What join does, precisely `t.join()` is an instance method called on a **target thread** `t`. The thread that *calls* it (the caller) is suspended until `t` **terminates** — i.e., `t`'s `run()` method returns or throws, putting `t` in the `TERMINATED` state. While waiting, the caller is in the **`WAITING`** state (or **`TIMED_WAITING`** if you pass a timeout). ```java Thread worker = new Thread(() -> doWork()); worker.start(); worker.join(); // main thread blocks here until worker ends useResultsOf(worker); // safe: worker has finished ``` ## Overloads and the timeout - `join()` — wait **indefinitely** until the thread dies. - `join(long millis)` — wait **at most** `millis`; if the thread is still running when time runs out, `join` simply **returns anyway**. - `join(long millis, int nanos)` — finer-grained timeout. Because a **timed** join can return *before* the thread finishes, you must check **`t.isAlive()`** afterward to know whether it actually completed or you just timed out. (A plain untimed `join()` always means the thread is done when it returns.) ## How it works under the hood `join` is implemented with **`wait/notify`** on the thread object. The thread's monitor is used internally; when a thread terminates, the JVM performs a `notifyAll` on it, waking any joiners. Note a subtle pitfall: because `Thread` itself uses its own monitor for `join`, you should **never** call `wait`/`notify` on a `Thread` object yourself — you'd interfere with `join`'s mechanism. ## Memory visibility — a key guarantee `join` establishes a **happens-before** relationship: everything the joined thread did **before it terminated** is **guaranteed visible** to the caller **after** `join` returns. So you can read the worker's results without extra synchronization. This is one of the main reasons join is safe and useful, not just convenient. ## Interruption `join` throws **`InterruptedException`**: another thread can `interrupt()` the waiter to make it stop waiting early. You must handle or propagate that checked exception. Interrupting the *joiner* does not stop the *target* thread — it only ends the wait. ## Edge cases - Joining a thread that **already finished** returns immediately. - Joining a thread that was **never started** also returns immediately (it's considered not alive). - `join(0)` means "wait forever," the same as `join()` (0 = no timeout, not zero wait). ## Modern alternatives For coordinating *many* tasks, raw `join` gets clumsy. Prefer `ExecutorService` + `Future.get()`, `CompletableFuture`, `CountDownLatch`, or (Java 21+) **structured concurrency** (`StructuredTaskScope`) which joins a whole group of tasks cleanly.

  • After t.join(5000) returns, how do you know whether t actually finished?
    Check t.isAlive(). A timed join returns when the thread dies OR when the timeout elapses, so isAlive() distinguishes 'finished' (false) from 'timed out, still running' (true).
  • Does join() guarantee you can safely read data the joined thread wrote?
    Yes. join establishes a happens-before edge: all writes the thread made before terminating are visible to the caller after join returns, so no extra synchronization is needed to read those results.

saying these in an interview costs you the question

  • Thinking join() stops or kills the target thread — it only waits for it to finish naturally.
  • Assuming a timed join() means the thread finished — it may have just timed out; check isAlive().
  • Believing join(0) waits zero time — 0 means wait indefinitely.
  • Forgetting the happens-before visibility guarantee and adding redundant synchronization, or worse, reading results before joining.

context

open as a page

What does Thread.sleep() do, and how does it differ from Object.wait() regarding locks?

level: juniorimportance: must knowfreq 78%

basics

~10 s

Thread.sleep() pauses the current thread for a given time but keeps any locks it holds. wait() also pauses the thread but releases the lock on the object so other threads can use it.

open as a page

sleep() and join() throw InterruptedException. What does that mean and how should you handle it?

level: middleimportance: must knowfreq 58%

basics

~10 s

InterruptedException means another thread asked this thread to stop waiting early (via interrupt()). Don't swallow it: either let it propagate, or catch it and restore the interrupt flag by calling Thread.currentThread().interrupt().

open as a page

What is Thread.yield(), and why is it rarely the right tool?

level: middleimportance: should knowfreq 48%

basics

~20 s

Thread.yield() is a hint that the current thread is willing to give up the CPU so other threads can run. The scheduler is free to ignore it, so it guarantees nothing and is rarely needed.

open as a page

How do Java thread priorities work, and how much should you rely on them?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

You can set a thread's priority from 1 to 10 (default 5) as a hint that it should get more CPU time. But priorities are advisory and depend on the OS, so you should not rely on them for correctness.

open as a page