Thread State and Priorities: How the JVM Schedules, Pauses, and Advances Your Code
Learn Java thread states (NEW→RUNNABLE→BLOCKED→WAITING→TERMINATED) and priorities — how the JVM decides which thread runs and when.
Thread State and Priorities: How the JVM Schedules, Pauses, and Advances Your Code
Learn Java thread states (NEW→RUNNABLE→BLOCKED→WAITING→TERMINATED) and priorities — how the JVM decides which thread runs and when.
Section 2 — Start Simple (Zero → Basic)
Imagine you’re at a busy airport. There are many passengers (threads), but only a few security gates (CPU cores) are open. Some passengers are in line waiting (RUNNABLE), some are sitting at the lounge waiting for a boarding call (WAITING), some are stuck because the gate agent needs their passport checked (BLOCKED), and one is already on the plane (TERMINATED). The airport can only process a few passengers at a time, and a VIP flyer (high-priority thread) might get moved ahead in line.
That, in a nutshell, is thread states and priorities.
Every thread in Java is always in exactly one of 6 states. The JVM and OS together decide who gets to “board” (use the CPU), for how long, and what happens when they have to step aside.
Section 3 — Build Intuition
Why does this exist? Your computer has 8–16 CPU cores. But a typical Java app has hundreds of threads — HTTP request handlers, background jobs, GC threads, RabbitMQ consumers, etc. There’s no way everyone runs at once. The JVM needs a traffic controller system.
The problem it solves: Without thread states and scheduling, threads would fight over the CPU chaotically, starving each other. With it, the JVM can:
- Park a thread when it’s waiting for a DB response (instead of wasting CPU spinning)
- Wake it up the moment data arrives
- Give critical threads (like payment processing) more CPU time than background tasks
Section 4 — Step-by-Step Working
Let’s walk through the 6 thread states and their transitions one step at a time.

Here’s what each transition means in plain language:
Step 1 — NEW: You call new Thread(...). The thread object exists in memory but hasn't started. No CPU resources consumed yet.
Step 2 — RUNNABLE: You call .start(). The thread is now either actively running on a CPU core, or sitting in the OS scheduler's ready queue. Java combines both into one state — you can't tell from the JVM side which it is.
Step 3 — BLOCKED: The thread tries to enter a synchronized block but another thread holds the lock. It parks here, consuming zero CPU, until the lock is free.
Step 4 — WAITING: The thread called object.wait(), Thread.join(), or LockSupport.park() with no timeout. It'll stay here forever until someone explicitly wakes it via notify() or unpark().
Step 5 — TIMED_WAITING: Same as WAITING but with a deadline — Thread.sleep(1000), wait(timeout), parkNanos(...). Automatically wakes up after time expires.
Step 6 — TERMINATED: The run() method returned (or threw an uncaught exception). The thread is dead — you cannot restart it.
Section 5 — Visual Learning: Thread Priority
Now let’s look at priorities — how the JVM decides who goes first.

Section 6 — Code + Explanation
Simple example: Observing thread states
public class ThreadStateDemo {
public static void main(String[] args) throws InterruptedException {
// 1. Thread is created — state: NEW
Thread worker = new Thread(() -> {
try {
// 3. Inside run() — state: RUNNABLE
Thread.sleep(2000); // state becomes: TIMED_WAITING
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
System.out.println(worker.getState()); // → NEW
worker.start(); // → RUNNABLE
System.out.println(worker.getState()); // → RUNNABLE
Thread.sleep(100); // give worker time to sleep
System.out.println(worker.getState()); // → TIMED_WAITING
worker.join(); // wait for it to finish
System.out.println(worker.getState()); // → TERMINATED
}
}
Line by line:
new Thread(...)— allocates the thread object, state = NEW, nothing running yetworker.start()— tells the JVM to schedule it; state flips to RUNNABLEThread.sleep(100)— after the worker callssleep(2000)internally, its state is TIMED_WAITINGworker.join()— the main thread blocks untilworkerfinishes- After join — the worker is TERMINATED
Advanced example: Observing BLOCKED vs WAITING
java
public class BlockedVsWaiting {
private static final Object LOCK = new Object();
public static void main(String[] args) throws InterruptedException {
// Thread A holds the lock and waits on it
Thread threadA = new Thread(() -> {
synchronized (LOCK) {
try {
LOCK.wait(); // releases lock, enters WAITING
} catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
}, "Thread-A");
// Thread B tries to grab the same lock → BLOCKED (can't enter sync block)
Thread threadB = new Thread(() -> {
synchronized (LOCK) { // BLOCKED here until A releases (which wait() does)
System.out.println("Thread-B got the lock");
}
}, "Thread-B");
threadA.start();
Thread.sleep(100); // let A acquire lock and call wait()
threadB.start();
Thread.sleep(100); // let B try to acquire lock
System.out.println("A = " + threadA.getState()); // → WAITING
System.out.println("B = " + threadB.getState()); // → BLOCKED
synchronized (LOCK) { LOCK.notify(); } // wake up A
}
}
What changed and why: Here we see the critical difference — wait() releases the lock and parks the thread (WAITING), while Thread B can't even enter the synchronized block and is stuck BLOCKED.
Section 7 — Connect to Real-World Systems
In Spring Boot @Async methods: When you use thenComposeAsync(fn, executor), the CompletableFuture framework parks the continuation in WAITING until the dependent stage completes, then re-submits it to your executor pool as RUNNABLE. The state transitions happen entirely inside the framework.
In Kubernetes HPA and thread dumps: When you see many threads in BLOCKED state in a thread dump — that’s contention on a shared lock. When you see many in TIMED_WAITING — likely too many sleep() calls or timeouts in your code. This is exactly the signal you'd look for when diagnosing the connection issues under load.
Section 8 — Go Deeper (Intermediate → Advanced)
Advanced: Priority internals + edge cases
OS-level reality: Java thread priorities map to OS-level priorities. On Linux (which your pods run on), the JVM uses pthread with SCHED_OTHER policy by default. The OS treats priorities as "niceness hints," not hard guarantees. Two threads with priority 10 and 5 might get equal CPU time if the OS decides so. This is why you should never depend on priorities for correctness — only use them as performance hints.
Thread starvation: If you have many high-priority threads, low-priority threads may never run — this is called starvation. In practice, modern OSes prevent it with aging (gradually boosting priority of waiting threads), but it’s a real risk with custom schedulers.
Priority inheritance in synchronized blocks: Java does not implement priority inheritance for monitors. If a high-priority thread is blocked waiting for a low-priority thread holding a lock, the low-priority thread does not get boosted. This can cause priority inversion — a known hazard in real-time systems.
Section 9 — Common Mistakes
Mistake 1: “RUNNABLE means the thread is running” Wrong. RUNNABLE means it’s either running OR waiting for a CPU turn. A RUNNABLE thread may sit in the OS run queue for milliseconds before getting CPU time. Don’t assume RUNNABLE = active work.
Mistake 2: “Setting priority 10 makes my thread faster” Not necessarily. The OS is the final arbiter. On Linux with SCHED_OTHER, all Java threads compete under the same policy. MAX_PRIORITY only means it might get scheduled slightly more aggressively — not that it runs exclusively.
Mistake 3: “I can restart a TERMINATED thread” False. Once terminated, the thread object is dead. Calling .start() again throws IllegalThreadStateException. You need new Thread(...).
Mistake 4: “BLOCKED and WAITING are the same thing” They feel similar (both = parked) but the cause and wake mechanism are totally different. BLOCKED = lock competition (wakes automatically when lock is free). WAITING = explicit coordination (wakes only on notify() or unpark()). This distinction is critical when reading thread dumps.
Mistake 5: “Thread.sleep() holds locks while sleeping” False. sleep() does NOT release any monitor locks the thread holds. If a thread holds a synchronized lock and calls sleep(), other threads trying to acquire that lock remain BLOCKED for the entire sleep duration. Use wait() if you want to release the lock while pausing.
Section 10 — Summary
- Java threads have 6 states: NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED
- NEW = object exists, not scheduled. RUNNABLE = running or in OS queue. BLOCKED = waiting for a monitor lock. WAITING = parked indefinitely by
wait()/park(). TIMED_WAITING = parked with a deadline. TERMINATED = dead, non-restartable - BLOCKED vs WAITING is the most important distinction: BLOCKED is about lock competition, WAITING is about explicit coordination
sleep()keeps locks;wait()releases them- Thread priorities (1–10) are hints, not guarantees. Default is
NORM_PRIORITY (5) - Priority inversion (low-priority thread holding a lock a high-priority thread needs) is a real hazard
- A thread dump showing many BLOCKED threads = lock contention. Many WAITING threads = normal for well-designed consumer pools
One-line intuition: A thread is like a person in multiple possible waiting situations — in a queue for the cashier (RUNNABLE), locked out of a room (BLOCKED), waiting for a phone call (WAITING), or taking a timed nap (TIMED_WAITING).
Section 11 — Conclusion
You’ve gone from “what even is a thread state?” all the way to priority inversion, reading thread dumps, and writing monitoring code for production services. That’s a solid arc.
The concept of thread states is one of those things that separates engineers who “write concurrent code” from engineers who truly understand it
THANKS FOR READING
메타데이터
- post_id
- b39272f8fea5
- slug
- thread-state-and-priorities-how-the-jvm-schedules-pauses-and-advances-your-code-b39272f8fea5
- url
- https://medium.com/@Codio.dev/thread-state-and-priorities-how-the-jvm-schedules-pauses-and-advances-your-code-b39272f8fea5
- canonical_url
- https://medium.com/@Codio.dev/thread-state-and-priorities-how-the-jvm-schedules-pauses-and-advances-your-code-b39272f8fea5
- author_url
- https://medium.com/@Codio.dev
- status
- ok
- fetched_at
- 2026-07-13 06:23:13