Back to Thread Basics & Overview
Q5Question 5 of 7

What is the difference between start() and run()?

Key Concepts
  • start() creates a new OS thread and then calls run() on that new thread, enabling true parallelism.
  • Calling run() directly is just a normal method call on the current thread with no concurrency.
  • start() can only be called once per Thread object; a second call throws IllegalThreadStateException.
  • Internally, start() delegates to the native start0() method, which calls pthread_create on Linux or CreateThread on Windows.
  • Calling run() instead of start() is one of the most common multithreading bugs in beginner code.
  • The difference is visible by checking Thread.currentThread().getName() inside the run method.

Definition

start() creates a new OS thread and calls run() on it for true parallelism, while calling run() directly executes the method on the current thread with no concurrency.
  • start() – creates a new OS-level thread, adds it to the thread group, and the JVM calls run() on that new thread. The calling thread continues in parallel immediately after start() returns.
  • run() – when called directly, it is just an ordinary method invocation on the current thread. No new thread is created. The calling thread is blocked until run() completes. There is no concurrency.
  • Single use – start() can only be called once per Thread object. The thread must be in the NEW state. Calling start() on a RUNNABLE or TERMINATED thread throws IllegalThreadStateException.
  • Native mechanism – internally, start() calls the native method start0(), which invokes the OS thread creation API (pthread_create on Linux, CreateThread on Windows).

start() is like hiring a new teller and telling them to go serve customers at their own counter. The new teller works in parallel with you. run() is like you walking over to the other counter and doing the work yourself. No new teller was hired. You left your own counter, did the work sequentially, and came back. There is no parallelism.

Understanding the Concepts

What does start() do?

When you call start() on a Thread object, the JVM performs the following steps:

  1. Checks that the thread is in the NEW state. If not, throws IllegalThreadStateException.
  2. Adds the thread to its ThreadGroup.
  3. Calls the native start0() method, which requests the OS to create a new thread.
  4. The new OS thread calls this.run().
  5. The calling thread returns from start() immediately, without waiting for run() to complete.
JAVA
package com.bank.core;

public class StartDemo {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            System.out.println("Task on: " + Thread.currentThread().getName());
        }, "SettlementWorker");

        t.start(); // new OS thread created, runs in parallel
        System.out.println("Main continues on: " + Thread.currentThread().getName());
    }
}
TEXT
Main continues on: main
Task on: SettlementWorker

The output order may vary because both threads run in parallel.

What does calling run() directly do?

Calling run() directly is just an ordinary method call. The method executes on the current thread. No new thread is created.

JAVA
package com.bank.core;

public class RunDirectlyDemo {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            System.out.println("Task on: " + Thread.currentThread().getName());
        }, "SettlementWorker");

        t.run(); // NO new thread! Runs on main thread.
        System.out.println("Main continues on: " + Thread.currentThread().getName());
    }
}
TEXT
Task on: main
Main continues on: main

Both print statements show "main" because run() executed on the main thread. The output order is always the same because there is no concurrency.

How do the two compare?

| Feature | start() | run() called directly |
|---|---|---|
| New thread created | Yes | No |
| Execution thread | New OS thread | Current thread |
| Parallelism | Yes | No |
| Thread state change | NEW to RUNNABLE | No state change |
| Can call twice | No (IllegalThreadStateException) | Yes (just a method call) |

Why can start() only be called once?

The first call to start() transitions the thread from NEW to RUNNABLE. After run() completes, the thread enters TERMINATED. Neither RUNNABLE nor TERMINATED can transition back to NEW. Calling start() again would violate the lifecycle contract.

JAVA
package com.bank.core;

public class DoubleStartDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> System.out.println("Processing trade"));
        t.start();
        t.join();

        try {
            t.start(); // TERMINATED thread cannot restart
        } catch (IllegalThreadStateException e) {
            System.out.println("Cannot restart terminated thread");
        }
    }
}
TEXT
Processing trade
Cannot restart terminated thread

What is the real-world impact of this bug?

Calling run() instead of start() causes sequential execution instead of parallel execution. In a batch processing system, this can be a severe performance regression.

JAVA
package com.bank.service;

import java.util.ArrayList;
import java.util.List;

public class BatchProcessor {
    public static void main(String[] args) throws InterruptedException {
        List<Thread> threads = new ArrayList<>();
        long startTime = System.currentTimeMillis();

        for (int i = 0; i < 5; i++) {
            final int batch = i;
            Thread t = new Thread(() -> {
                try { Thread.sleep(200); } // simulate 200ms of work
                catch (InterruptedException e) { Thread.currentThread().interrupt(); }
                System.out.println("Batch " + batch + " done");
            });
            threads.add(t);
            t.start(); // CORRECT: parallel execution
            // t.run(); // WRONG: sequential, 5 x 200ms = 1000ms
        }

        for (Thread t : threads) t.join();
        long elapsed = System.currentTimeMillis() - startTime;
        System.out.println("Total time: " + elapsed + "ms");
        // With start(): ~200ms (parallel)
        // With run():   ~1000ms (sequential)
    }
}
TEXT
Batch 0 done
Batch 1 done
Batch 2 done
Batch 3 done
Batch 4 done
Total time: 210ms

What are common mistakes?

Besides the run-vs-start mistake, another common error is calling start() inside a loop without keeping a reference to join later, leading to fire-and-forget threads that may not complete before the program exits.

JAVA
// WRONG: no join, main may exit before threads finish
for (int i = 0; i < 10; i++) {
    new Thread(() -> settleTradeOrder(order)).start();
}
// Main exits here. Threads may be killed mid-execution.

// CORRECT: collect threads and join
List<Thread> workers = new ArrayList<>();
for (int i = 0; i < 10; i++) {
    Thread t = new Thread(() -> settleTradeOrder(order));
    workers.add(t);
    t.start();
}
for (Thread t : workers) t.join();

What should a senior engineer know?

  • start0() native method: on HotSpot, start0() calls os::create_thread() which calls pthread_create() on Linux. The new OS thread runs Thread::run() through a JNI callback.
  • Thread creation cost: each start() call creates an OS thread with a default 1MB stack. For high-throughput systems, use a thread pool to avoid this per-task cost.
  • Daemon threads: calling t.setDaemon(true) before start() makes the thread a daemon. The JVM exits when only daemon threads remain, even if they have not finished. Use for background tasks like log flushing.

Visual Overview

DIAGRAM
  start() path:
  ┌──────────┐  start()   ┌──────────────┐  start0()  ┌──────────────┐
  │  Thread   │──────────▶│ JVM internals │──────────▶│ OS creates    │
  │  (NEW)    │           │ check state   │           │ new thread    │
  └──────────┘           │ add to group  │           │ calls run()   │
                          └──────────────┘           └──────────────┘
                                                          │
                          Main thread continues           │ New thread
                          immediately (parallel)          │ executes run()
                                                          ▼

  run() path:
  ┌──────────┐  run()     ┌──────────────────────────┐
  │  Thread   │──────────▶│ Executes on CURRENT thread │
  │  (NEW)    │           │ No new thread created       │
  └──────────┘           │ Sequential, no parallelism  │
                          └──────────────────────────┘

Key Takeaway

start() creates a new OS thread and calls run() on it, enabling true parallel execution. The calling thread continues immediately. run() called directly is just an ordinary method invocation on the current thread: no new thread, no parallelism, no concurrency. This distinction is one of the most common sources of multithreading bugs in beginner code.

The internal mechanism matters: start() calls the native start0(), which requests the OS to create a thread (via pthread_create on Linux). This has a real cost of about 1ms and 1MB of stack per thread. That is why production code uses thread pools instead of calling start() on raw Thread objects.

Remember one sentence: start() creates a new OS thread for parallel execution, while calling run() directly just runs the method sequentially on the current thread.

---

Share
Use ← → arrow keys to navigate