Thread Lifecycle in Java
A thread moves through six states, and knowing which one it is in is the first step in diagnosing any hang.
-
Java Basics
- Introduction to Java
- Setting Up Java and Writing Your First Program
- Variables, Data Types and Literals in Java
- Type Casting and Type Conversion in Java
- Operators and Expressions in Java
- Input and Output in Java
- Comments, Keywords and Naming Conventions in Java
- Control Flow in Java: if, else and switch
- Loops in Java: for, while and do-while
- Methods
- Arrays and Strings
-
OOP
- Classes and Objects in Java
- Constructors in Java
- The this Keyword in Java
- The static Keyword in Java
- Encapsulation in Java
- Access Modifiers in Java
- Inheritance in Java
- Method Overriding and super in Java
- Polymorphism in Java
- Abstraction, Abstract Classes and Interfaces in Java
- Composition, Aggregation and Association in Java
- The Object Lifecycle in Java
- Core Java
- Exception Handling
-
Collections
- The Java Collections Framework
- List in Java: ArrayList, LinkedList, Vector and Stack
- Set in Java: HashSet, LinkedHashSet and TreeSet
- Map in Java: HashMap, LinkedHashMap and TreeMap
- How HashMap Works Internally in Java
- Queue and Deque in Java: ArrayDeque and PriorityQueue
- Iterators in Java
- Comparable and Comparator in Java
- Collections Utilities and Choosing the Right Collection
- Generics
- Java 8+
- Stream API
- Date and Time
- File and I/O
-
Multithreading
- Threads in Java: Processes, Runnable and Thread
- Thread Lifecycle in Java
- Synchronization in Java: synchronized and volatile
- Locks and Atomic Classes in Java
- Race Conditions and Deadlocks in Java
- The Executor Framework and Thread Pools in Java
- Future and CompletableFuture in Java
- Concurrent Collections in Java
- The Java Memory Model
- JVM and Memory
- Advanced Java
- Networking
- JDBC
- Testing
The six states
start()
NEW ---------------------> RUNNABLE
| ^
synchronized blocked | | wait(), join(), park()
v | v
BLOCKED WAITING
|
sleep(t), wait(t) v
TIMED_WAITING
|
run() ends v
TERMINATED| State | Meaning |
|---|---|
NEW | Created but start() has not been called |
RUNNABLE | Eligible to run; may or may not be on a processor right now |
BLOCKED | Waiting to acquire a monitor lock |
WAITING | Waiting indefinitely for another thread to act |
TIMED_WAITING | Waiting with a timeout |
TERMINATED | The run method has completed or thrown |
Java has no separate "running" state. RUNNABLE covers both ready and actually executing, because the JVM leaves that distinction to the operating system scheduler.Observing the states
Object lock = new Object();
Thread worker = new Thread(() -> {
synchronized (lock) {
try {
lock.wait(); // WAITING
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
System.out.println(worker.getState()); // NEW
worker.start();
Thread.sleep(50);
System.out.println(worker.getState()); // WAITING
synchronized (lock) {
lock.notify();
}
worker.join();
System.out.println(worker.getState()); // TERMINATEDWhat moves a thread out of RUNNABLE
| Call | State entered | Releases the lock |
|---|---|---|
Thread.sleep(ms) | TIMED_WAITING | No |
object.wait() | WAITING | Yes |
object.wait(ms) | TIMED_WAITING | Yes |
thread.join() | WAITING | No |
Entering a contended synchronized block | BLOCKED | Not applicable |
lock.lock() when held | WAITING | No |
The column that matters is the last one. sleep keeps every lock it holds; wait gives up the monitor it was called on. Confusing the two produces deadlocks that are hard to explain.
wait, notify and notifyAll
public class MessageBox {
private final Object lock = new Object();
private String message;
public void put(String value) {
synchronized (lock) {
message = value;
lock.notifyAll(); // wake every waiter
}
}
public String take() throws InterruptedException {
synchronized (lock) {
while (message == null) { // always a loop, never an if
lock.wait();
}
String value = message;
message = null;
return value;
}
}
}Three rules govern these methods:
- They may only be called while holding the monitor of that object.
- The condition must be tested in a
whileloop, because a thread can wake without a matchingnotify. That is called a spurious wakeup, and it is permitted by the specification. - Prefer
notifyAll.notifywakes one arbitrary waiter, which can be the wrong one and can leave the system stalled.
BLOCKED against WAITING
// BLOCKED: queued for a monitor, nothing to do but wait for the owner to leave
synchronized (lock) { }
// WAITING: voluntarily suspended until another thread signals
synchronized (lock) { lock.wait(); }A thread that is BLOCKED is competing for a lock. A thread that is WAITING has given one up and needs to be woken. In a thread dump, many BLOCKED threads on one lock indicate contention; many WAITING threads usually indicate a missing signal.
Reading a thread dump
"worker-3" #21 prio=5 os_prio=0 tid=0x00007f... nid=0x1a03 waiting for monitor entry
java.lang.Thread.State: BLOCKED (on object monitor)
at com.example.notes.Counter.increment(Counter.java:14)
- waiting to lock <0x000000076ab3f1c8> (a java.lang.Object)
- locked <0x000000076ab3f1d0> (a java.lang.Object)The state, the line, and the identity of the lock being waited on are all there. Two threads each holding what the other waits for is a deadlock, and the JVM reports it explicitly at the end of the dump.
Priorities are only a hint
worker.setPriority(Thread.MAX_PRIORITY); // 10
worker.setPriority(Thread.MIN_PRIORITY); // 1Thread priority is passed to the operating system scheduler, which may ignore it entirely. Correctness must never depend on it.
Modern alternatives
BlockingQueue<String> queue = new LinkedBlockingQueue<>();
queue.put("message"); // blocks when full
String value = queue.take(); // blocks when emptyAlmost every use of wait and notify is better expressed with a BlockingQueue, a CountDownLatch, a Semaphore or a Condition. Learn the low level mechanism to understand what happens, then use the higher level tools.
Common mistakes
- Calling
waitornotifyoutside asynchronizedblock, which throwsIllegalMonitorStateException. - Testing the condition with
ifinstead ofwhile. - Using
notifywherenotifyAllis needed. - Expecting
sleepto release a lock. - Relying on thread priority for correctness.
- Calling
waiton one object while synchronising on another.
Best practices
- Always wait in a loop that rechecks the condition.
- Prefer
notifyAll. - Prefer the
java.util.concurrentutilities towaitandnotify. - Name threads so dumps are readable.
- Take a thread dump before guessing at the cause of a hang.
Practice
- Print the state of a thread before
start, during asleep, and afterjoin. - Explain the difference between
BLOCKEDandWAITINGin your own words. - Why must the condition around
waitbe awhileloop? - Implement a one slot message box, then rewrite it with an
ArrayBlockingQueue. - Why is it wrong to rely on
setPriorityto control ordering?
Conclusion
Six states, and the important distinction is whether a waiting thread still holds its lock. Learn wait and notify to understand thread dumps, and then use the higher level concurrency utilities in real code.