Multithreading basics
Threads vs processes, Runnable, start vs run, sleep, join, interrupts and thread states.
Your laptop's CPU has several cores, and a web server must handle thousands of requests at once. Multithreading lets a Java program do several things at the same time: download files while keeping the UI responsive, process many requests in parallel, or split a big computation across cores. This lesson covers the fundamentals: creating threads, their lifecycle, sleep, join and interruption.
Processes and threads#
- A process is a running program with its own memory. Your Java application is one process (one JVM).
- A thread is an independent path of execution inside a process. All threads in a process share the same heap (objects), but each has its own call stack (local variables).
Every Java program starts with one thread, called main. The JVM also runs background threads, such as the garbage collector.
Concurrency means making progress on multiple tasks over the same period (possibly by switching between them); parallelism means literally running at the same instant on different cores. Threads give you both.
Creating a thread#
The work a thread performs is a Runnable: a functional interface with a void run() method. Pass one to a Thread and call start():
Other ways to create threads:
Prefer passing a Runnable (or using an executor, next-but-one lesson) over subclassing Thread.
start() vs run()
Calling start() twice on the same thread throws IllegalThreadStateException. A thread runs once.
Threads run concurrently and unpredictably#
With several threads, the scheduler decides who runs when. The interleaving changes from run to run:
Possible outputs:
or AABBBABAAB, or anything else. Never assume an order between threads unless you enforce one with synchronisation tools.
Sleeping and waiting: sleep and join#
Thread.sleep(ms)pauses the current thread. It throws the checkedInterruptedException.t.join()makes the current thread wait fortto finish.t.join(1000)waits at most one second.
Running the two tasks in parallel took ~500 ms rather than 500 ms + the fast task's time. With slow I/O (network calls, disk), threads let waiting overlap.
Thread states#
A thread moves through these states (thread.getState()):
Interrupting a thread#
Java has no safe way to forcibly kill a thread. Instead, you ask it to stop with interrupt(), and the thread cooperates:
How interruption works:
t.interrupt()sets the thread's interrupted flag.- If the thread is blocked in
sleep,waitorjoin, it wakes up immediately with anInterruptedException, and the flag is cleared. - Long-running loops should check
Thread.currentThread().isInterrupted().
Never swallow
InterruptedExceptionwith an empty catch. Either propagate it (declarethrows InterruptedException) or restore the flag withThread.currentThread().interrupt()so code higher up knows a stop was requested.
Daemon threads#
By default the JVM keeps running until all non-daemon threads finish. A daemon thread is a background helper that doesn't keep the JVM alive:
Use daemons for non-critical background work; never for tasks that must complete, like saving a file, because they are abandoned abruptly at exit.
Uncaught exceptions in threads#
An exception thrown inside a thread's run() kills that thread only, and prints a stack trace. It does not reach main's try/catch:
Executors (two lessons ahead) make getting results and exceptions back from threads much easier.
A practical example: parallel downloads#
Creating a raw thread per task works for a handful of tasks but doesn't scale to thousands. Platform threads are expensive (each reserves a stack, typically around 1 MB). That's why real applications use thread pools or, in Java 21, virtual threads.
The big catch: shared data#
All threads share the heap. When two threads read and write the same variable at the same time, results can be wrong in surprising ways. That's a race condition, and it's the subject of the next lesson.
Common mistakes#
- Calling
run()instead ofstart(). - Forgetting
join(), somainreads results before workers have finished. - Swallowing
InterruptedException. - Assuming threads execute in a particular order.
- Creating thousands of platform threads directly; use executors.
- Using deprecated
Thread.stop(),suspend()orresume().
What's next#
Next, synchronisation and thread safety: why count++ breaks with two threads, and the tools (synchronized, volatile, atomics, locks and concurrent collections) that fix it.
Check your understanding
Quick quiz
1.What is the difference between calling
thread.start()andthread.run()?2.What does
t.join()do?3.What is the recommended way to ask a thread to stop?
Finished reading?
Mark this lesson complete to track your progress.