When a program runs, the operating system gives it resources: memory, file access, CPU time. To do several things at once, you need to understand how processes and threads work.
Process and thread: what's the difference
A process is a running program. Every process has its own isolated memory: one process cannot accidentally touch another's data. When you launch a Java application with java -jar app.jar, the operating system creates a single process.
A thread is a unit of execution inside a process; a process may hold one or many. All threads of the same process share the same memory area: the heap, static fields, open files. This is what makes threads a powerful tool — and a source of bugs if you use them carelessly.
A short formula: a process is a container of resources, a thread is a unit of work inside that container.
Why you need multithreading
Imagine a server that handles HTTP requests. Single-threaded, the second request waits until the first one finishes — even though the first mostly just waits for a response from the database.
Multithreading solves two problems:
- Responsiveness. While one thread waits for I/O (network, disk, database), another thread keeps working. The program does not "freeze".
- Core utilization. Modern CPUs have 4, 8, 16 or more cores. A single-threaded program uses one core. Threads let you engage all available cores for parallel computation.
How to start a thread: Thread and Runnable
Java has two basic ways to describe a task.
Way 1 — extending Thread:
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread running: " + Thread.currentThread().getName());
}
}
Thread t = new MyThread();
t.start(); // starts a new thread; run() executes in it
Way 2 — implementing Runnable (preferred):
Runnable task = () -> System.out.println("Thread running: " + Thread.currentThread().getName());
Thread t = new Thread(task);
t.start();
Runnable is preferred: the class does not "spend" its single inheritance on Thread, and the task stays separated from the mechanism that runs it.
start() vs run(): a common mistake
A frequent mistake is calling run() instead of start(). The code looks fine, nothing fails — there simply is no second thread.
Left: run() is an ordinary method call, the work happens right inside main, and main waits while it runs. Right: start() makes the JVM spin up a second thread, main moves on without waiting, and join() brings the lines back together.
The thread name in the output gives it away:
live example
public class Demo {
public static void main(String[] args) throws InterruptedException {
Runnable task = () -> System.out.println("running in thread " + Thread.currentThread().getName());
Thread t1 = new Thread(task, "worker-1");
t1.run(); // wrong: an ordinary method call
Thread t2 = new Thread(task, "worker-2");
t2.start(); // right: the JVM creates a thread and calls run() in it
t2.join();
}
}
Run
Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →
It prints main, then worker-2: after t1.run() the thread name did not change, because that is an ordinary method call. start() asks the JVM to create a system thread and call run() inside it.
Waiting for completion: join()
start() returns immediately — the new thread lives on its own. If you need its result, the calling thread must wait explicitly, with join().
live example
public class JoinDemo {
public static void main(String[] args) throws InterruptedException {
int[] result = new int[1];
Thread worker = new Thread(() -> {
try {
Thread.sleep(100); // long-running work
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
result[0] = 42;
});
worker.start();
System.out.println("right after start(): " + result[0]);
worker.join();
System.out.println("after join(): " + result[0]);
}
}
Run
Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →
The first line prints 0 — the work has not finished yet; the second prints 42, because join() blocked main until worker finished. A timeout is possible: worker.join(5000) waits no longer than 5 seconds.
Daemon threads
A daemon thread is a background thread that the JVM does not wait for when the program ends. Once all non-daemon threads have finished, the JVM shuts down on its own, aborting all daemons.
live example
public class DaemonDemo {
public static void main(String[] args) throws InterruptedException {
Thread cleaner = new Thread(() -> {
while (true) {
System.out.println("cleaning the cache");
try {
Thread.sleep(150);
} catch (InterruptedException e) {
return;
}
}
});
cleaner.setDaemon(true); // set BEFORE start()
cleaner.start();
Thread.sleep(400);
System.out.println("main thread finished — the JVM exits and kills the daemon");
}
}
Run
Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →
The loop prints "cleaning the cache" a few times, but as soon as main ends the JVM exits and cuts the daemon off mid-flight. Without setDaemon(true) the program would never finish. Daemons carry garbage collection, monitoring, cache cleanup; user work stays in regular threads.
Thread states
Every thread has a lifecycle. You can get it via t.getState():
NEW— the thread is created,start()has not been called yet.RUNNABLE— running or ready to run (the OS scheduler decides when to give it the CPU).BLOCKED/WAITING/TIMED_WAITING— the thread is waiting for something (a monitor, a notification, a timeout).TERMINATED—run()finished.
One arrow looks surprising: after notify() a thread goes to BLOCKED, not straight to RUNNABLE. It gave the monitor up to sleep in wait(), and the monitor is still held by whoever called notify() — so the woken thread queues up for it again, and in a thread dump that looks exactly like BLOCKED.
The cost of an OS thread
Creating a thread is not cheap. Every OS thread requires:
- allocating a stack (usually 512 KB — 1 MB by default);
- a system call to create the thread;
- scheduler overhead for context switching.
In practice: do not create a thread per request in a loop. To manage a pool, use ExecutorService — covered in the article on thread pools.
Thread vs task
A thread is an OS mechanism. A task (Runnable, Callable) is what you want to execute — different things, not to be conflated.
| Thread | Task | |
|---|---|---|
| What it is | OS resource | Work logic |
| Creation | new Thread(...) | lambda, class |
| Who manages it | JVM + OS | programmer / pool |
| Cost | high | low |
Good practice: describe tasks (Runnable / Callable) and hand them to a pool (ExecutorService) instead of managing threads by hand. Managing them yourself stays relevant mainly for understanding the fundamentals.
Virtual threads (Java 21)
Java 21 brought virtual threads — lightweight threads managed by the JVM rather than the OS. You can create them by the millions without exhausting system resources:
Thread vt = Thread.ofVirtual().start(() -> {
System.out.println("virtual thread");
});
Virtual threads are ideal for I/O-bound tasks. For CPU-intensive computation you still use regular threads. More details in the article on virtual threads.
In short
- A process is an isolated running program; a thread is a unit of execution inside it, sharing memory.
- Multithreading gives responsiveness (not waiting on I/O) and use of all CPU cores.
start()creates a new thread,run()is a method call in the current one;Runnablebeats extendingThread.join()blocks the caller until another thread finishes; the JVM does not wait for daemons on exit.- Creating an OS thread is expensive — stack, system call, context switch; under load you take
ExecutorService. - Java 21 gives you virtual threads — a lightweight alternative for I/O scenarios.
What to read next
- Java Memory Model — how threads see each other's changes and what
happens-beforeis. - Data races — what happens when two threads read and write one variable without synchronization.
- Executors and thread pools — how to manage tasks through a pool instead of threads by hand.
- Virtual threads in Java 21 — how they differ from OS threads.