How the start() and run() Methods Work in Java Multithreading

Illustration for How the start() and run() Methods Work in Java Multithreading
By Last updated:

When working with threads in Java, two methods often confuse developers: start() and run(). While both are part of the Thread class, they behave very differently. Misunderstanding these can lead to subtle bugs and performance issues in your applications.

In this tutorial, we’ll demystify these methods with clear explanations, examples, and best practices—ensuring your multithreaded Java code works exactly as intended.


The Core Difference: start() vs run()

Feature start() run()
Thread Creation Starts a new thread of execution Does not start a new thread
Executes in A new thread The current calling thread
Lifecycle Triggers full thread lifecycle No thread lifecycle management
Usage Preferred way to begin thread execution Used internally by the thread

What is run()?

  • run() is just a normal method defined in the Runnable interface or overridden in the Thread class.
  • If you call it directly, it runs on the current thread, just like a regular method.
public class MyRunnable implements Runnable {
    public void run() {
        System.out.println("Running in: " + Thread.currentThread().getName());
    }
}

Runnable r = new MyRunnable();
r.run(); // executes on the current thread, no new thread created

What is start()?

  • start() is a method of the Thread class that actually starts a new thread.
  • Behind the scenes, it delegates execution to the thread’s run() method on a new call stack.
Thread t = new Thread(() -> {
    System.out.println("Running in: " + Thread.currentThread().getName());
});
t.start(); // launches a new thread

Visualizing Thread Lifecycle

NEW → RUNNABLE → RUNNING → TERMINATED
  • start() transitions a thread from NEW → RUNNABLE
  • JVM scheduler picks it and calls run() → RUNNING
  • When done → TERMINATED

Common Mistake: Calling run() Instead of start()

Thread t = new Thread(() -> {
    System.out.println("This runs in the main thread!");
});
t.run();  // ❌ No new thread is started!

Always use:

t.start();  // ✅ Spawns a new thread

Real-World Analogy

Imagine you're the manager of a factory:

  • run() is like doing the work yourself
  • start() is like hiring a worker to do the job while you move on to other tasks

Java Internals and Memory Model

  • Calling start() registers the thread in the JVM scheduler
  • Java Memory Model ensures that thread-local stacks and visibility rules apply only when a new thread is started
  • volatile and synchronized work properly only across real threads

Coordination Tools

  • join() waits for a thread to complete
  • sleep() pauses the current thread
  • yield() hints to the scheduler to switch threads
  • wait()/notify() handle communication between threads

Example: Runnable + ExecutorService

ExecutorService executor = Executors.newFixedThreadPool(2);
executor.submit(() -> {
    System.out.println("Running with ExecutorService");
});
executor.shutdown();

Java Version Tracker

📌 What's New in Java Versions?

Java 8

  • Lambdas simplify thread creation
  • CompletableFuture for async flows

Java 9

  • Flow API (Reactive Streams)

Java 11+

  • Minor improvements to thread management

Java 21

  • Virtual Threads (lightweight threads via Thread.startVirtualThread())
  • Structured Concurrency
  • Scoped Values for safer shared data

Best Practices

  • Always use start() to begin thread execution
  • Use ExecutorService for better thread pooling
  • Avoid direct Thread instantiation unless necessary
  • Never call run() expecting concurrency

Common Pitfalls

  • Confusing run() with start()
  • Thread leakage from unclosed executors
  • Race conditions when starting multiple threads on shared state
  • Catching InterruptedException but not resetting the interrupt flag

Design Patterns That Use start()/run()

  • Thread-per-Message
  • Worker Thread
  • Future Task Pattern

Conclusion and Key Takeaways

  • start() creates a new thread, run() does not
  • Always use start() when you want concurrent behavior
  • Misusing run() leads to sequential, not parallel, execution
  • Use high-level concurrency APIs to manage threads safely

FAQs

Q1: Can I call start() twice on the same thread?
A: No. It throws IllegalThreadStateException.

Q2: What happens if run() throws an exception?
A: It’s caught by the JVM unless handled manually. It can terminate the thread.

Q3: Can I override run() and not use Runnable?
A: Yes, by extending Thread directly, but implementing Runnable is cleaner.

Q4: What’s the lifecycle of a thread started with start()?
A: NEW → RUNNABLE → RUNNING → TERMINATED

Q5: Why doesn't run() create a new thread?
A: Because it’s just a method; it needs the JVM to spawn a new thread via start().

Q6: How is ExecutorService better than manual threads?
A: It reuses threads, improves performance, and offers better control.

Q7: Can virtual threads use run()?
A: No. You still use start() or structured concurrency APIs.

Q8: Is it ever valid to call run() directly?
A: Only when testing logic, not for concurrency.

Q9: How do I know if my thread is running?
A: Use Thread.getState() or log Thread.currentThread().getName().

Q10: How to stop a running thread safely?
A: Use interrupt() and check Thread.interrupted() within the run loop.