Multithreading
Processes vs threads, the thread lifecycle, ExecutorService, and virtual threads (Project Loom).
On this page
So far your programs did one thing at a time. Multithreading lets them do many things at once - download files while updating a UI, or handle thousands of users simultaneously. It's powerful, and famously tricky. Let's build a solid mental model.
Processes vs. threads
A process is a running program with its own memory. A thread is a single path of execution within a process. One process can run many threads, all sharing the same memory.
A restaurant kitchen is a process. Each cook is a thread. Multiple cooks work simultaneously, sharing the same fridge and counters (shared memory). More cooks means more dishes at once - but they must coordinate, or two will grab the same pan and chaos ensues. That coordination is the whole challenge of concurrency.
Creating threads
The modern way is to pass a task (a Runnable) to a thread:
Thread t = new Thread(() -> {
System.out.println("Running on: " + Thread.currentThread().getName());
});
t.start(); // runs the task on a NEW thread
t.join(); // wait for it to finishstart() vs. run()
Call start(), not run(). start() launches a new thread; calling run()
directly just executes the code on the current thread - no concurrency at all.
A classic beginner mistake.
The thread lifecycle
A thread moves through several states:
| State | Meaning |
|---|---|
| NEW | Created, not yet started |
| RUNNABLE | Running or ready to run |
| BLOCKED / WAITING | Paused, waiting for a lock or another thread |
| TIMED_WAITING | Waiting for a set time (e.g. sleep) |
| TERMINATED | Finished |
Don't manage threads by hand - use an ExecutorService
Creating raw threads for every task is wasteful and hard to manage. An
ExecutorService manages a pool of reusable threads for you:
import java.util.concurrent.*;
ExecutorService pool = Executors.newFixedThreadPool(4);
for (int i = 1; i <= 10; i++) {
int taskId = i;
pool.submit(() -> System.out.println("Task " + taskId));
}
pool.shutdown(); // stop accepting new tasks; finish the queued onesInstead of hiring a new waiter for every single customer (creating a thread per task - expensive!), a restaurant keeps a fixed team of waiters who serve customer after customer. A thread pool reuses a fixed set of threads to handle a queue of tasks - far more efficient.
Getting results back with Future
submit returns a Future - a handle to a result that will be ready later:
Future<Integer> future = pool.submit(() -> {
Thread.sleep(100);
return 40 + 2;
});
Integer result = future.get(); // blocks until the result is ready
System.out.println(result); // 42Virtual threads (Project Loom)
Modern Java (21+) introduced virtual threads - extremely lightweight threads. You can run millions of them, making it easy to write simple, blocking-style code that scales massively:
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 10_000; i++) {
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return "done";
});
}
} // all 10,000 tasks run concurrently - cheaplyWhy virtual threads are a big deal
Traditional (platform) threads are heavy - a few thousand and you run out of memory. Virtual threads are so cheap you can have a million, so servers can handle huge numbers of concurrent requests with simple, readable code. They're one of the most important recent additions to Java.
Quick check
Why should you usually use an ExecutorService instead of creating new Thread objects for every task?
Key takeaways
- A process has its own memory; threads run within a process and share its memory.
- Start threads with start() (not run()); join() waits for completion.
- Prefer an ExecutorService thread pool over creating raw threads per task.
- submit() returns a Future - a handle to a result that becomes available later.
- Virtual threads (Java 21+) are ultra-lightweight, enabling millions of concurrent tasks with simple code.
Sharing memory between threads is where things get dangerous. Next, we'll confront race conditions, deadlocks, and the principles of safe concurrency.