Loslegen
Javaneer
Zurück zur Stufe
Stufe 4·Fortgeschrittenes Java

Multithreading

Processes vs threads, the thread lifecycle, ExecutorService, and virtual threads (Project Loom).

22 Min. LesezeitExperte

Deutsche Übersetzung in Arbeit

Diese Lektion ist noch nicht ins Deutsche übersetzt und wird daher auf Englisch angezeigt. Der Rest der Seite ist vollständig lokalisiert.

Auf dieser Seite

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 kitchen with many cooks

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 finish

start() 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:

StateMeaning
NEWCreated, not yet started
RUNNABLERunning or ready to run
BLOCKED / WAITINGPaused, waiting for a lock or another thread
TIMED_WAITINGWaiting for a set time (e.g. sleep)
TERMINATEDFinished

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 ones
A thread pool is like a team of waiters

Instead 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);      // 42

Virtual 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 - cheaply

Why 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.