skip to content

Thread Basics

Creating, starting, pausing, interrupting and inspecting threads, plus the lifecycle states they move through. Every concurrency interview starts here, usually with the difference between start() and run().

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

explore

questions

25

What is the difference between calling start() and calling run() directly on a Thread?

level: juniorimportance: must knowfreq 88%

answer

  1. start() = new thread; run() directly = current thread
  2. Calling run() = ordinary method call, no concurrency
  3. start() returns immediately; caller and new thread run in parallel
  4. Proof: Thread.currentThread().getName() shows main vs Thread-0
  5. start() once only → second call throws IllegalThreadStateException

basics

~20 s

start() creates a new thread and runs your run() code on it, so it executes concurrently. Calling run() directly just runs the code on the current thread, like an ordinary method call — no new thread, no concurrency.

solid answer

~40 s

start() is the method that actually creates concurrency. It asks the JVM to allocate a new thread, registers it with the scheduler, and that new thread then invokes run(). The calling thread returns from start() immediately and both run in parallel. Calling run() directly is just a normal method call: it executes the run() body synchronously on the current thread, no new thread is spawned, and there is zero concurrency. So if you call run() instead of start(), everything is sequential and 'main' prints, say, the worker's output before continuing. A second consequence: start() may be called only once per Thread object — calling it again throws IllegalThreadStateException — whereas run() is just a method and could be called repeatedly (but pointlessly). This start-vs-run mix-up is a classic interview trap.

code

java · 6 lines
java
Runnable task = () -> System.out.println("running on " + Thread.currentThread().getName());

Thread t = new Thread(task);
t.run();    // prints: running on main      (no new thread)
t.start();  // prints: running on Thread-0  (a new thread)
// t.start(); // would throw IllegalThreadStateException

go deeper

for a junior

State the bottom line: start() spawns a new thread, run() called directly runs on the current thread with no concurrency.

for a middle

Explain that start() returns immediately and is native, demonstrate the difference with Thread.currentThread().getName(), and know start() is one-shot.

for a senior

Discuss why the bug is silent (run() compiles and 'works'), the IllegalThreadStateException on re-start, and why this pushes teams toward executors that hide thread lifecycle.

for a principal

Use it to argue against hand-managed Thread objects entirely: lifecycle correctness (start-once, no restart) is exactly the error-prone surface that ExecutorService / structured concurrency abstract away.

## The two methods A `Thread` object has two relevant methods: - **`run()`** — contains the task. It is an ordinary method whose body is your code. - **`start()`** — the trigger that spawns a *new* thread and arranges for that thread to call `run()`. ## What start() actually does `start()` is native, low-level plumbing. When you call it, the JVM: 1. Creates a new operating-system (or virtual) thread. 2. Marks the Thread object's state as started and hands it to the scheduler. 3. On that **new** thread, invokes `run()`. Meanwhile the **calling** thread does not wait — `start()` returns right away, so the caller and the new thread now run **concurrently**. This is the only way to get a second thread of execution from a Thread object. ## What calling run() directly does `run()` is just a method. If you write `t.run()`, the **current** thread executes the body synchronously, top to bottom, then control returns — exactly like calling any other method. **No new thread is created and there is no concurrency at all.** A quick way to see it: ```java Thread t = new Thread(() -> System.out.println("on " + Thread.currentThread().getName())); t.run(); // prints: on main (runs on the caller's thread) t.start(); // prints: on Thread-0 (runs on a brand-new thread) ``` With `run()` the name is `main`; with `start()` it is a separate thread name — proof that only `start()` switched threads. ## Why people get this wrong Both method names sound like 'go'. But `run()` is the *content* and `start()` is the *engine*. Calling `run()` 'works' (no error, your code executes), which makes the bug silent: the program is simply single-threaded when you expected parallelism. ## One-shot rule A Thread object models one execution. You may call `start()` **at most once**; a second call throws `IllegalThreadStateException`. To run the task again you need a new Thread (or, better, submit the Runnable to a thread pool). `run()`, being a plain method, has no such restriction — but calling it directly is almost always a mistake. ## Takeaway Use `start()` to get concurrency. Calling `run()` directly silently degrades your code to sequential execution on the current thread.

  • What happens if you call start() twice on the same Thread object?
    It throws IllegalThreadStateException. A Thread instance represents a single execution and cannot be restarted; create a new Thread or use a thread pool to run the task again.
  • Does calling run() directly ever make sense?
    Rarely. You might call it from inside your own run() of a subclass, or invoke a Runnable synchronously when you deliberately want no extra thread — but as a way to 'start a thread' it is always a bug.

saying these in an interview costs you the question

  • Saying run() starts a new thread — it does not; it runs on the current thread.
  • Claiming start() runs run() on the calling thread — it runs run() on the NEW thread.
  • Believing you can restart a thread by calling start() again — that throws IllegalThreadStateException.
  • Thinking calling run() throws an error so you'd notice — it silently runs single-threaded.

context

open as a page

What are the two main ways to define the work a thread runs in Java, and how do they differ?

level: juniorimportance: must knowfreq 78%

basics

~20 s

You can extend the Thread class and override its run() method, or implement the Runnable interface's run() method and hand it to a Thread. Both put your code in run(); Runnable just separates the task from the thread.

open as a page

What does it mean to interrupt a thread in Java, and what does calling interrupt() actually do?

level: juniorimportance: must knowfreq 70%

basics

~10 s

Calling interrupt() on a thread just sets a boolean flag on it asking it to stop. It does not forcibly kill the thread. The thread must check the flag and decide to stop itself.

open as a page

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

level: juniorimportance: must knowfreq 72%

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.

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

What are the six thread states defined by Java's Thread.State enum, and what does each one mean?

level: juniorimportance: must knowfreq 70%

basics

~20 s

A Java thread is in one of six states: NEW (created, not started), RUNNABLE (running or ready to run), BLOCKED (waiting for a lock), WAITING (waiting indefinitely for another thread), TIMED_WAITING (waiting with a timeout), and TERMINATED (finished).

open as a page

Why is implementing Runnable generally preferred over extending Thread?

level: middleimportance: must knowfreq 80%

basics

~20 s

Runnable keeps your task separate from the thread, so you can still extend another class, reuse the task, and hand it to a thread pool. Extending Thread locks your class into the thread machinery and uses up Java's single inheritance.

open as a page

What is the difference between the instance method isInterrupted() and the static method Thread.interrupted()?

level: middleimportance: must knowfreq 75%

basics

~10 s

Both read the interrupt flag. isInterrupted() (instance) just reads it and leaves it unchanged. The static Thread.interrupted() reads it for the current thread AND clears it back to false.

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 the difference between the BLOCKED and WAITING thread states, and what transitions a thread into each?

level: middleimportance: must knowfreq 60%

basics

~20 s

BLOCKED means a thread is waiting to get a lock so it can enter a synchronized block. WAITING means a thread chose to wait for another thread to signal it, by calling wait(), join(), or park() with no timeout.

open as a page

You catch an InterruptedException in a method that cannot propagate it. How should you handle it correctly, and why is swallowing it wrong?

level: seniorimportance: must knowfreq 80%

basics

~10 s

Don't ignore it. Either rethrow it, or — if you can't — call Thread.currentThread().interrupt() to set the flag again so callers up the stack still know the thread was asked to stop.

open as a page

What is the difference between a daemon thread and a user thread in Java, and how do you make a thread a daemon?

level: juniorimportance: should knowfreq 55%

basics

~20 s

A user thread keeps the program alive; the JVM waits for all user threads to finish before exiting. A daemon thread is a background helper that does not keep the program alive. Call thread.setDaemon(true) before start() to make one.

open as a page

Walk through the complete lifecycle of a thread from NEW to TERMINATED. What triggers the start and end transitions, and why can't a thread be restarted?

level: juniorimportance: should knowfreq 45%

basics

~20 s

A thread starts in NEW after you create it. Calling start() moves it to RUNNABLE. While running it may pass through BLOCKED, WAITING, or TIMED_WAITING. When run() finishes (or throws) it becomes TERMINATED. A terminated thread can't be started again.

open as a page

What does Thread.currentThread() return, and when is it useful?

level: middleimportance: should knowfreq 55%

basics

~20 s

Thread.currentThread() is a static method that returns the Thread object for whichever thread is executing that line right now. It's handy for logging or debugging — for example printing getName() to see which thread ran your code.

open as a page

What happens when an exception escapes a thread's run() method, and how does UncaughtExceptionHandler let you handle it?

level: middleimportance: should knowfreq 60%

basics

~20 s

If an exception is thrown and not caught inside run(), it propagates out and kills only that thread — the rest of the program keeps running. You can register an UncaughtExceptionHandler (per thread or a default for all threads) to log or react to such failures instead of just printing a stack trace.

open as a page

Which Java methods throw InterruptedException, and what do they do to the interrupt status flag when they throw?

level: middleimportance: should knowfreq 58%

basics

~10 s

Blocking methods like Thread.sleep(), Object.wait(), Thread.join(), and BlockingQueue.take()/put() throw InterruptedException. When they throw, they clear the interrupt flag back to false.

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

Why does idiomatic modern Java rarely create Thread objects directly, and what replaces hand-managed threads?

level: seniorimportance: should knowfreq 60%

basics

~20 s

Creating raw threads is costly and hard to manage. Instead you write Runnable or Callable tasks and submit them to an ExecutorService, which reuses a pool of threads and handles their lifecycle, queuing, and shutdown for you.

open as a page

Why is it dangerous to rely on a daemon thread for resource cleanup or flushing data, and what should you use instead?

level: seniorimportance: should knowfreq 45%

basics

~20 s

When the last user thread finishes, the JVM exits and kills daemon threads abruptly — without running their finally blocks or letting them finish current work. So a daemon flushing a buffer or closing a file may be stopped mid-operation and lose data. Use explicit shutdown (executor shutdown, shutdown hooks, or user threads) for work that must complete.

open as a page

How would you design a long-running worker thread that responds promptly and correctly to cancellation?

level: seniorimportance: should knowfreq 62%

basics

~20 s

Use interruption: have the worker check Thread.currentThread().isInterrupted() in its loop and catch InterruptedException around its blocking calls (restoring the flag). To cancel, call interrupt() — don't invent your own stop flag if interruption already works.

open as a page

Why does Java's RUNNABLE state cover both a thread actively running and a thread blocked on I/O, and what are the consequences for diagnosing performance with thread dumps?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Java's RUNNABLE lumps together threads on a CPU, threads queued to run, and threads waiting on I/O, because the JVM can't reliably tell these apart from the OS. So 'RUNNABLE' in a dump doesn't prove a thread is actually using CPU.

open as a page

Is it safe to use Thread.getState() to coordinate threads or make control-flow decisions? Explain why, and what to use instead.

level: principalimportance: should knowfreq 25%

basics

~20 s

No. getState() is a diagnostic snapshot that can change the instant after you read it, so deciding logic based on it is racy. Use real synchronization tools — locks, latches, conditions, or wait/notify — to coordinate threads.

open as a page

Why must setDaemon(true) be called before start(), and what does a freshly created thread's daemon status default to?

level: middleimportance: nice to knowfreq 35%

basics

~20 s

Once a thread is running, the JVM has already decided whether it counts toward keeping the program alive, so you can't change it — calling setDaemon after start() throws IllegalThreadStateException. A new thread defaults to the daemon status of whichever thread created it.

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

Walk through exactly how the JVM decides when to exit based on running threads, and how shutdown hooks, daemons, and System.exit interact during that sequence.

level: principalimportance: nice to knowfreq 28%

basics

~20 s

The JVM exits once no user (non-daemon) threads are left running, or when something calls System.exit(). At that point it runs all registered shutdown hooks, and only after those finish does it stop daemon threads and end the process. Daemons never delay exit; hooks get a window to clean up.

open as a page