Threads
A thread is an independent path of execution within a program. Every Java program has at least one thread automatically — the main thread, which runs your main() method — but a program can create additional threads that run concurrently, letting it do more than one thing at once (or at least appear to, depending on the number of CPU cores available).
Extending Thread
The most direct way to create a thread is to subclass Thread and override its run() method with the code that thread should execute.
A custom Thread subclass
Java
class CounterThread extends Thread {
@Override
public void run() {
for (int i = 1; i <= 3; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
CounterThread worker = new CounterThread();
worker.start(); // schedules run() to execute on a new thread
System.out.println("Main thread continues immediately");
}
}Main thread continues immediately Thread-0: 1 Thread-0: 2 Thread-0: 3
Note
The exact interleaving of output between the main thread and Thread-0 isn't guaranteed — on a different run, the "Main thread continues" line could print after some or all of the counter output. That unpredictability is inherent to concurrent execution and is exactly why thread coordination (covered on the Synchronization page) matters.
start() vs. Calling run() Directly
Calling run() does not create a new thread
Calling worker.run() instead of worker.start() is a common beginner mistake. run() is just a regular method — calling it directly executes that code synchronously on whichever thread made the call, with no concurrency at all. Only start() actually asks the JVM to allocate a new thread of execution and have that thread call run() for you.
run() vs. start() — the difference is observable
Java
CounterThread a = new CounterThread();
CounterThread b = new CounterThread();
System.out.println("Calling run() directly:");
a.run(); // runs on the main thread — blocks until finished, no concurrency
System.out.println("Calling start():");
b.start(); // runs on a genuinely new threadCalling run() directly: main: 1 main: 2 main: 3 Calling start(): Thread-1: 1 Thread-1: 2 Thread-1: 3
Notice the thread name in the first block is main — proof that run() executed on the main thread rather than spawning anything new. Only the second block, using start(), produces a distinct thread name.
Why Runnable Is Usually Preferred
Extending Thread works, but it has a real limitation: Java doesn't support extending more than one class, so a class that extends Thread can't extend anything else. It also mixes "what work needs doing" with "how that work gets run" into a single class. Implementing the Runnable interface instead separates those concerns — the next page covers this pattern, which is the one used in the vast majority of real Java code.
Call | What happens |
|---|---|
thread.start() | JVM allocates a new thread, which then calls run() — genuinely concurrent |
thread.run() | Executes run() synchronously on the calling thread — no new thread at all |
Tip
If your program's output shows no new thread name and nothing runs concurrently even though you expected it to, check whether start() was accidentally written as run() — it's a one-word typo with a completely different meaning.
A thread is an independent path of execution; every program starts with a main thread
Extending Thread and overriding run() is the most direct way to define thread behavior
start() creates a new thread of execution; run() called directly does not
Implementing Runnable instead of extending Thread is the generally preferred, more flexible pattern