Java
Multithreading
Thread.start()
Thread.run()
Concurrency

Why we call Thread.start method which in turns calls run method?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

In Java, run() contains the code a thread should execute, but start() is what actually creates a new thread of execution. That distinction is not ceremony. It is how the JVM asks the operating system to schedule work on a separate call stack. Calling run() directly is just an ordinary method call on the current thread.

start() creates a real concurrent thread

When you call start(), the JVM transitions the thread from the NEW state into a runnable state and arranges for the new thread to invoke run().

java
1class Worker extends Thread {
2    @Override
3    public void run() {
4        System.out.println("run on: " + Thread.currentThread().getName());
5    }
6}
7
8public class Demo {
9    public static void main(String[] args) {
10        Worker worker = new Worker();
11        worker.start();
12        System.out.println("main on: " + Thread.currentThread().getName());
13    }
14}

You will usually see different thread names, which shows that the work really moved onto another thread.

Calling run() directly does not start a thread

If you call run() yourself, Java treats it as a normal method call:

java
1public class DirectRunDemo {
2    public static void main(String[] args) {
3        Worker worker = new Worker();
4        worker.run();
5        System.out.println("main on: " + Thread.currentThread().getName());
6    }
7}

Both lines execute on the main thread. No scheduler involvement, no new call stack, and no concurrency.

That is why Java exposes both methods. run() defines the task. start() begins threaded execution of that task.

The separation reflects thread lifecycle

A Thread object has a lifecycle. Roughly speaking:

  • new: object created, not started
  • runnable: start() called, eligible for execution
  • running or blocked: managed by the scheduler
  • terminated: run() finished

If run() alone started a new thread, the JVM would have no clean place to separate "what the thread should do" from "when the thread should actually begin."

This separation also lets the runtime enforce important rules. For example, a thread can be started only once.

java
Worker worker = new Worker();
worker.start();
// worker.start();  // throws IllegalThreadStateException

That restriction makes sense only because start() is a lifecycle operation, not just another method call.

Why this matters in real code

Using run() accidentally instead of start() causes subtle bugs:

  • expected parallel work runs serially
  • UI or request threads stay blocked
  • concurrency bugs disappear during testing because no real concurrency happened

That last point is particularly dangerous. Code may look correct in a small example because everything runs on one thread, then fail later when real threading is introduced.

Runnable makes the design clearer

Modern Java often separates the task from the thread object by using Runnable or executors:

java
Runnable task = () -> System.out.println(Thread.currentThread().getName());
Thread thread = new Thread(task);
thread.start();

Or with an executor:

java
1import java.util.concurrent.ExecutorService;
2import java.util.concurrent.Executors;
3
4ExecutorService pool = Executors.newFixedThreadPool(2);
5pool.submit(() -> System.out.println(Thread.currentThread().getName()));
6pool.shutdown();

These APIs reinforce the same idea: the code to run is one thing, and the mechanism that schedules it on a thread is another.

Common Pitfalls

The most common mistake is calling run() directly and assuming it creates concurrency. It does not.

Another common issue is treating start() as if it were just a convenience wrapper around run(). It is a lifecycle operation with JVM and scheduler involvement.

People also forget that a Thread can be started only once. If work must run again, create a new thread or use an executor.

Finally, raw Thread usage is often less maintainable than ExecutorService for real applications that need pooling, cancellation, or back-pressure.

Summary

  • 'run() defines the work; start() begins a new thread that will execute that work.'
  • Calling run() directly is just a normal method call on the current thread.
  • 'start() exists to manage thread lifecycle and scheduling.'
  • A thread can be started only once.
  • In modern Java, Runnable and executors usually express the design more clearly than subclassing Thread.

Course illustration
Course illustration

All Rights Reserved.