skip to content

Creating Threads: Thread vs Runnable

Extending Thread versus implementing Runnable, and why Runnable wins: it separates the task from the execution mechanism and stays poolable. The other half is the start() versus run() distinction — calling run() directly gives you no new thread at all, which is the classic screening question.

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

questions

5

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

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 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

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