03 How Threads Transition Between Six States

03 How Threads Transition Between Six States #

In this lesson, we will mainly learn how a thread transitions between the 6 states.

The 6 States of a Thread #

Just like a living organism goes through a process of birth, growth, and eventually death, a thread also has its own lifecycle. In Java, a thread can be in one of 6 states.

  1. New
  2. Runnable
  3. Blocked
  4. Waiting
  5. Timed Waiting
  6. Terminated

To determine the current state of a thread, you can use the getState() method. A thread can only be in one state at any given time.

New #

Let’s now go through each of the 6 states of a thread. First, let’s take a look at the top-left state called New.

img

New represents the state of a thread that has been created but not yet started. When we create a new thread using new Thread(), if the thread has not started running the start() method and hasn’t started executing the code inside the run() method, then its state is New. Once the thread calls start(), its state will transition from New to Runnable, as shown in the middle box in the state transition diagram.

Runnable #

img

In Java, the Runnable state corresponds to two states in the operating system thread state: Running and Ready. This means that a thread in the Runnable state can either be actively executing or waiting to be allocated CPU resources.

So, if a running thread is in the Runnable state and it gets interrupted in the middle of its task because the CPU is scheduled to do something else, its state will remain unchanged as Runnable. This is because it can be scheduled back at any time to continue executing its task.

Blocked #

img

Next, let’s take a look at the three boxes below Runnable, which are collectively referred to as the Blocked state. In Java, the Blocked state typically includes three states: Blocked, Waiting, and Timed Waiting. Now let’s see what each of these states means.

Blocked #

img

First, let’s look at the simplest state, Blocked. From the direction of the arrows, we can see that the only way to transition from Runnable to Blocked is when a thread enters synchronized code and fails to acquire the monitor lock. Whether entering a synchronized block or a synchronized method, the process is the same.

Now, let’s look to the right. When a thread in the Blocked state acquires the monitor lock, it transitions back to the Runnable state.

Waiting #

img

Next, let’s take a look at the Waiting state. There are three possibilities for a thread to enter the Waiting state.

  1. When calling the Object.wait() method without setting a timeout parameter.
  2. When calling the Thread.join() method without setting a timeout parameter.
  3. When calling the LockSupport.park() method. Just now it was emphasized that “Blocked” only applies to the synchronized monitor lock. However, in Java, there are many other locks, such as ReentrantLock. If a thread fails to acquire such a lock, it will enter the “Waiting” state because it essentially executes the LockSupport.park() method, which puts the thread into the “Waiting” state. Similarly, Object.wait() and Thread.join() also put the thread into the “Waiting” state.

The difference between “Blocked” and “Waiting” is that “Blocked” waits for other threads to release the monitor lock, while “Waiting” waits for a certain condition, such as the completion of the joined thread or the notification from notify()/notifyAll().

Timed Waiting #

img

Above the “Waiting” state is the “Timed Waiting” state. These two states are very similar, with the only difference being whether there is a time limit. In “Timed Waiting”, the thread will wait for a timeout and be automatically awakened by the system or by a signal before the timeout.

The following situations will put the thread into the “Timed Waiting” state:

  1. The Thread.sleep(long millis) method with a time parameter set.
  2. The Object.wait(long timeout) method with a time parameter set.
  3. The Thread.join(long millis) method with a time parameter set.
  4. The LockSupport.parkNanos(long nanos) and LockSupport.parkUntil(long deadline) methods with time parameters set.

After discussing how to enter these three states, let’s now look at how to transition from these three states to the next state.

img

To transition from the “Blocked” state to the “Runnable” state, the thread needs to acquire the monitor lock. Transitioning from the “Waiting” state to another state is more special because waiting is unlimited, meaning it won’t automatically resume no matter how long it takes.

img

The “Waiting” state can only transition to the “Runnable” state when LockSupport.unpark() is called, or when the joined thread finishes running or is interrupted.

img

If another thread calls notify() or notifyAll() to wake up a waiting thread, it will directly enter the “Blocked” state. Why is that? It’s because when the thread that wakes up the waiting thread calls notify() or notifyAll(), it must first hold the monitor lock. Therefore, the thread in the “Waiting” state cannot acquire the lock when it is awakened, and it will enter the “Blocked” state until the thread that wakes it up completes its execution and releases the monitor lock. Only then can the waiting thread have a chance to compete for the lock. If it manages to acquire the lock, it will transition from the “Blocked” state back to the “Runnable” state.

img

The same rationale applies to executing notify() and notifyAll() in the “Timed Waiting” state. They will first enter the “Blocked” state and then transition back to the “Runnable” state after successfully acquiring the lock.

img

Of course, for “Timed Waiting”, if the timeout expires and it can directly acquire the lock or the joined thread finishes running or is interrupted or LockSupport.unpark() is called, it will directly return to the “Runnable” state without going through the “Blocked” state.

Terminated #

img

Now let’s take a look at the last state, “Terminated”. There are two ways to enter this state:

  • The run() method finishes execution and the thread exits normally.
  • An uncaught exception occurs that terminates the run() method and ultimately terminates the thread unexpectedly.
Key Points to Note #

Finally, let’s discuss two key points about thread transitions:

  1. The thread’s states must follow the direction of the arrows. For example, a thread cannot go directly from the “New” state to the “Blocked” state; it must go through the “Runnable” state first.
  2. The thread lifecycle is irreversible: once it enters the “Runnable” state, it cannot return to the “New” state; once it is terminated, there can be no further state changes. Therefore, a thread can only have one occurrence of the “New” and “Terminated” states, and only states in between can transition between each other.