Create and start threads — Thread class, Runnable, and the lifecycle of a thread.
Published September 21, 2026
A thread is the smallest unit of execution within a process. Java supports multithreading natively, allowing multiple threads to run concurrently within the same JVM process, sharing heap memory.
Option 1: Extend Thread
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("Running in: " + Thread.currentThread().getName());
}
}
new MyThread().start(); // start() creates a new OS thread and calls run()
Option 2: Implement Runnable (preferred)
Runnable task = () -> System.out.println("Task in: " + Thread.currentThread().getName());
Thread t = new Thread(task, "worker-1");
t.start();
Prefer Runnable over extending Thread — it separates the task from the thread mechanism, and a class can only extend one class.
NEW → RUNNABLE → [RUNNING] → TERMINATED
↕
BLOCKED/WAITING/TIMED_WAITING
synchronized)Object.wait(), Thread.join())Thread.sleep(ms), LockSupport.parkNanos())run() completed or threw an exceptionThread t = new Thread(() -> { /* ... */ });
System.out.println(t.getState()); // NEW
t.start();
System.out.println(t.getState()); // RUNNABLE
t.join(); // wait for completion
System.out.println(t.getState()); // TERMINATED
Thread t = new Thread(task);
t.setName("processor-1"); // useful for debugging
t.setDaemon(true); // JVM exits even if daemon threads are running
t.setPriority(Thread.MAX_PRIORITY); // 1-10, default 5 (hint only)
t.start(); // begin execution
t.join(); // wait for this thread to finish
t.join(5000); // wait max 5 seconds
t.interrupt(); // request interruption
// Check interruption
if (Thread.currentThread().isInterrupted()) {
// clean up and stop
}
// Static methods
Thread.sleep(1000); // pause current thread (throws InterruptedException)
Thread.yield(); // hint to scheduler to yield CPU
Thread.currentThread(); // reference to currently running thread
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // IMPORTANT: restore interrupt flag
return; // exit cleanly
}
}
Never swallow InterruptedException silently — always restore the interrupt flag or re-throw.
// UNSAFE: counter++ is not atomic (read-modify-write)
private int counter = 0;
public void increment() { counter++; } // race condition!
// SAFE: use AtomicInteger
private AtomicInteger counter = new AtomicInteger();
public void increment() { counter.incrementAndGet(); }
Runnable's single method is void run() — no return value, and it cannot throw a checked exception. Callable<V>'s single method is V call() throws Exception — it returns a value and is allowed to throw a checked exception, which is exactly why ExecutorService.submit() accepts a Callable and hands back a Future<V> you can call .get() on, while execute() only accepts a Runnable and gives nothing back. Reach for Callable any time the task produces a result or can fail with a checked exception; Runnable is for pure side-effecting, non-failing work.
Both pause a thread, but they differ in exactly the way that matters under load: wait() (defined on Object, must be called from inside a synchronized block) releases the monitor lock it's called with while paused, letting other threads acquire that same lock and make progress. Thread.sleep() holds onto any locks the thread currently owns for the entire sleep duration — it has no awareness of locks at all, it just pauses the thread.
This difference has a concrete failure mode: a thread that calls Thread.sleep() while holding a lock blocks every other thread waiting on that same lock for the full sleep duration — a self-inflicted contention (or outright deadlock, if another thread is waiting on this one to release the lock before it can do the same) that's easy to introduce by reaching for sleep() inside a synchronized block when wait()/a Condition was the correct tool.
start() (creates new thread) and run() (executes in the calling thread — does NOT create a new thread).Thread.sleep() does NOT release locks — Object.wait() does.