Java Concurrency and Multithreading, Part 4: Thread States and How They Transition

Let's be clear about this: Java threads have 6 states. Online — including the copy-paste crowd on CSDN who mindlessly repost who-knows-what — everyone says 5. I went and read the docs myself, and I'm certain it's 6.

Series index: Java Intermediate/Advanced Programming: article index

Disclaimer up front: everything in this series is my own understanding of the subject, typed out by hand. It may well contain mistaken views or misunderstandings. Use it as reference only, and corrections are welcome.

First, to be precise: the thread states discussed here are thread states inside the JVM, not thread states in OS CPU scheduling. The ideas are similar, but they are not the same thing.

All demo code for this series is public at https://github.com/renfei/demo/tree/master/java/ConcurrentDemo

The 6 Java Thread States

Let’s be clear about this: Java threads have 6 states. Online — including the copy-paste crowd on CSDN who mindlessly repost who-knows-what — everyone says 5. I went and read the docs myself, and I’m certain it’s 6:

  • NEW: a thread that has not yet started is in this state.
  • RUNNABLE: a thread executing in the Java virtual machine is in this state.
  • BLOCKED: a thread blocked waiting for a monitor lock is in this state.
  • WAITING: a thread waiting indefinitely for another thread to perform a particular action is in this state.
  • TIMED_WAITING: a thread waiting for another thread to perform an action for up to a specified waiting time is in this state.
  • TERMINATED: a thread that has exited is in this state.

Special note: a thread can be in only one state at a given point in time. These states are JVM states and do not reflect any OS thread state.

Once started, a thread can move between Runnable, Blocked, Waiting, and Timed Waiting until it finally reaches Terminated and dies.

The 6 States in Detail

NEW

When you new Thread you get a thread object, and at that moment it’s in the NEW state.

RUNNABLE

RUNNABLE counts as one state but actually covers two possibilities, READY and RUNNING. The JDK source puts it this way: Thread state for a runnable thread. A thread in the runnable state is executing in the Java virtual machine but it may be waiting for other resources from the operating system such as processor.

Meaning: a runnable thread is executing in the JVM, but may be waiting on other resources from the OS such as the CPU. So there are two possibilities, READY and RUNNING:

  • READY: eligible to be scheduled, but not currently scheduled.
  • RUNNING: actually, genuinely running.

BLOCKED

The thread needs a lock to continue, and some other thread currently holds it, so it waits passively until it wins the lock, at which point it goes back to “ready”.

WAITING

The thread is waiting voluntarily and indefinitely; only someone waking it up returns it to ready.

TIMED_WAITING

The thread is waiting voluntarily but with a deadline: either someone wakes it, or it wakes itself once the time is up.

TERMINATED

When a thread’s run() method completes — or the main thread’s main() method does — we consider it terminated.

Thread Operations

Sample code for this section: https://github.com/renfei/demo/blob/master/java/ConcurrentDemo/src/main/java/net/renfei/demo/concurrent/ThreadOperationDemo.java

Naming a Thread

Every thread has a name; even if you don’t set one, there’s a default auto-incrementing one. You can pass a name at construction time:

Thread threadA = new Thread(myThread, "Thread A");

Thread Priority

You can also set a priority per thread, but note: it’s only a hint to the scheduler. A high-priority thread is not guaranteed to run before a low-priority one — it merely suggests whom to schedule first. Set it like this:

threadC.setPriority(Thread.MAX_PRIORITY);

Interrupting a Thread

To stop a thread, call interrupt(), and you can use isInterrupted() to check whether it has been interrupted:

// Check whether threadA has been interrupted
if (!threadA.isInterrupted()) {
    // Interrupt threadA
    threadA.interrupt();
}

join

If the current process needs another process’s result, or needs it to finish first, call join()to block the current process and wait for the joined one to complete before continuing.

yield

yield() is often described as yielding: give up the current CPU time slice so the scheduler can run the next process. Note it yields only once — the next time the scheduler picks it, it keeps running. That’s different from sleep().