Introducing Threads
A thread is the smallest unit of execution in a Java program. Every program starts with at least one thread — the main thread. Creating additional threads allows a program to do multiple things at once.
What Is a Thread?
Java threads are lightweight processes that share the same heap memory. The Java Virtual Machine schedules threads on available CPU cores. Because threads share memory, they can communicate efficiently — but they also introduce the risk of data corruption when not handled carefully.
Note: Java 21 introduced Virtual Threads (Project Loom) as a preview feature. The OCP exam focuses on traditional platform threads. Virtual threads are a runtime implementation detail and do not change the core concurrency API.
Thread Lifecycle
A thread passes through well-defined states during its lifetime:
| State | Description |
|---|---|
NEW |
Thread object created but start() not yet called |
RUNNABLE |
Thread is executing or ready to execute on the CPU |
BLOCKED |
Waiting to acquire an intrinsic lock (e.g., entering synchronized block) |
WAITING |
Waiting indefinitely for another thread (e.g., join(), wait()) |
TIMED_WAITING |
Waiting for a specified period (e.g., sleep(ms), join(ms)) |
TERMINATED |
Thread has finished execution |
javaThread t = new Thread(() -> System.out.println("Hello"));
System.out.println(t.getState()); // NEW
t.start();
System.out.println(t.getState()); // RUNNABLE (or TERMINATED if very fast)Creating Threads: Two Approaches
Option 1 — Extending Thread
javapublic class PrintTask extends Thread {
private final String message;
public PrintTask(String message) {
this.message = message;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ": " + message);
}
}
PrintTask t = new PrintTask("Hello from thread");
t.start(); // starts a new thread; never call run() directlyOption 2 — Implementing Runnable
javapublic class PrintRunnable implements Runnable {
private final String message;
public PrintRunnable(String message) {
this.message = message;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ": " + message);
}
}
Thread t = new Thread(new PrintRunnable("Hello from Runnable"));
t.start();Prefer Runnable over extending Thread. It keeps the task logic separate from thread management, and a class implementing Runnable can still extend another class.
Lambda shorthand (since Runnable is a functional interface):
javaThread t = new Thread(() -> System.out.println("Lambda thread"));
t.start();Key Thread Methods
Thread.sleep(long millis)
Pauses the current thread for at least the specified duration. It does not release any locks the thread holds.
javatry {
System.out.println("Before sleep");
Thread.sleep(1000); // sleep 1 second
System.out.println("After sleep");
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // restore interrupted status
}Exam tip:
sleep()throws a checkedInterruptedException. Always restore the interrupt flag by callingThread.currentThread().interrupt()inside the catch block if you cannot propagate the exception.
join()
Makes the calling thread wait until the target thread finishes.
javaThread worker = new Thread(() -> {
// long computation
});
worker.start();
worker.join(); // main thread waits here until worker finishes
System.out.println("Worker is done");join(long millis) is a timed variant — the calling thread waits at most the given number of milliseconds.
interrupt()
Signals a thread to stop what it is doing. If the thread is blocked in sleep() or join(), it receives an InterruptedException. If the thread is running, the interrupt flag is set and the thread must check Thread.interrupted() or isInterrupted() to respond.
javaThread t = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
// do work
}
System.out.println("Thread interrupted, stopping");
});
t.start();
t.interrupt(); // request cancellationDaemon Threads
A daemon thread runs in the background and does not prevent the JVM from exiting. Mark a thread as daemon before calling start():
javaThread logger = new Thread(() -> { /* background logging */ });
logger.setDaemon(true);
logger.start();When all non-daemon (user) threads finish, the JVM exits even if daemon threads are still running.
start() vs run()
| Call | Effect |
|---|---|
t.start() |
Spawns a new OS thread; run() executes in that thread |
t.run() |
Executes run() in the calling thread — no new thread created |
Calling run() directly is a common mistake. It compiles and runs, but no concurrency occurs.
Key Rules Summary
- Use
implements Runnable(or a lambda) rather thanextends Thread. - Call
start(), neverrun(), to create a new thread. sleep()throwsInterruptedException— always handle it.join()blocks the current thread until the target thread terminates.- Restore the interrupt flag with
Thread.currentThread().interrupt()when catchingInterruptedException.
References
- Oracle Docs — Thread
- OCP Study Guide, Chapter 13 — Concurrency