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 theRunnable
interface or overridden in theThread
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 theThread
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 yourselfstart()
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
andsynchronized
work properly only across real threads
Coordination Tools
join()
waits for a thread to completesleep()
pauses the current threadyield()
hints to the scheduler to switch threadswait()/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()
withstart()
- 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.