DEV Community

Ishan Soni
Ishan Soni

Posted on

Java Multithreading — Thread states and introduction to thread profiling

Thread states

A Thread can be in any one of the following states at any point in time:

Thread states

NEW

This is the state of a thread when it is created but not yet started. For example, when you create a new Thread object using the constructor, the thread is in the new state. You can only call the start() method on a new thread; otherwise, an IllegalThreadStateException will be thrown.

RUNNABLE

This is the state of a thread when it is ready to run and waiting for CPU time. For example, when you call the start() method on a new thread, or when a blocked or waiting thread becomes eligible to run again. The thread scheduler selects a thread from the runnable state for execution, based on its priority and other factors.

RUNNING

This is the state of a thread when it is executing on the CPU. For example, when the thread scheduler picks a thread from the runnable state and assigns it to a CPU core, the thread is in the running state. The thread executes its run() method and performs its tasks. A thread can enter the running state only from the runnable state.

BLOCKED

This is the state of a thread when it is waiting to acquire a monitor lock that is held by another thread. For example, when a thread tries to enter a synchronized block or method that is already occupied by another thread, the thread is blocked until the lock is released. A thread can enter the blocked state from the running state, and can return to the runnable state when it acquires the lock.

WAITING

This is the state of a thread when it is waiting indefinitely for another thread to perform a certain action. For example, when a thread calls the wait() method on an object, or the join() method on another thread, or the park() method of LockSupport class, the thread is in the waiting state. A thread can enter the waiting state from the running state, and can return to the runnable state when it receives a notification or an interruption.

TIMED WAITING

This is the state of a thread when it is waiting for a specified amount of time for another thread to perform a certain action. For example, when a thread calls the sleep() method, or the wait() method with a timeout parameter, or the join() method with a timeout parameter, or the parkNanos() or parkUntil() methods of LockSupport class, the thread is in the timed waiting state. A thread can enter the timed waiting state from the running state, and can return to the runnable state when the timeout expires or when it receives a notification or an interruption.

TERMINATED

This is the state of a thread when it has completed its execution or has been aborted due to an exception or an error. For example, when a thread finishes its run() method normally or abnormally, or when it is stopped by calling the stop() method (which is deprecated), the thread is in the terminated state. A thread can enter the terminated state only from the running state, and cannot return to any other state.

What is the state of a thread when it is waiting for an I/O operation to complete? — It’s either Blocked or Timed Waiting, depending on the type of I/O operation and the API used. When a thread performs a traditional blocking I/O operation, such as reading a file or a socket, it enters the blocked state and waits for the I/O operation to finish. However, some I/O operations may have a timeout parameter that specifies how long the thread should wait for the operation to complete. In this case, the thread enters the timed waiting state and waits for either the operation to finish or the timeout to expire.

Can the OS put a thread executing a synchronised block into the blocked state mid way and schedule a blocked thread that is waiting for the lock? — No. This is because the synchronized block ensures that only one thread can enter the block at a time, and the thread that enters the block acquires the monitor lock of the object that is used for synchronization. The OS cannot interrupt or preempt a thread that holds a monitor lock, unless the thread voluntarily releases the lock by exiting the block or by calling wait() on the object.

Thread Profiling

Thread Profiling can help us in understanding what states threads are in and why. We can also see if the threads are able to run in parallel or not (i.e see the concurrency level of threads). It also tells us how much time threads spend in different states. We can also find and analyse cases of high lock contention. We’ll use Java Flight Recorder (JFR) to profile threads.
What is Java Flight Recorder and how to enable it and do a flight recording? — JFR

When we profile our application using JFR, it’ll show different thread events. Let’s try to map these thread events to the states we discussed above:

Thread profiling

Java Thread Parked

This event occurs when a thread calls the LockSupport.park() method or its variants, which causes the thread to suspend its execution until it is unparked by another thread or a timeout expires. This is often used by concurrency utilities, such as java.util.concurrent.locks, to implement efficient blocking mechanisms. This event corresponds to the Waiting or Timed Waiting state, depending on whether the park() method was called with a timeout parameter or not.

Java Thread Sleep

This event occurs when a thread calls the Thread.sleep() method or its variants, which causes the thread to suspend its execution for a specified amount of time. This is often used to introduce delays or pauses in the execution flow. This event corresponds to the Timed Waiting state.

Java Monitor Wait

This event occurs when a thread calls the Object.wait() method or its variants, which causes the thread to release the monitor (lock) of an object and wait until it is notified by another thread or a timeout expires. This is often used to implement inter-thread communication and coordination. This event corresponds to the Waiting or Timed Waiting state, depending on whether the wait() method was called with a timeout parameter or not.

Java Monitor Blocked

This event occurs when a thread tries to acquire the monitor lock (inside a synchronized method/block) of an object that is already owned by another thread, which causes the thread to block until the monitor is released. This is often used to implement mutual exclusion and synchronization. This event corresponds to the Blocked state.

Socket Read

This event occurs when a thread calls the SocketInputStream.read() method or its variants, which causes the thread to read data from a socket. This is often used to implement network communication. This event corresponds to the Blocked state.

Socket Write

This event occurs when a thread calls the SocketOutputStream.write() method or its variants, which causes the thread to write data to a socket. This is also often used to implement network communication. This event corresponds to the Blocked state.

File Read

This event means that the thread is reading data from a file by using the read() method of the File class or its subclasses. This method can block the thread until some data is available or an exception occurs. This event corresponds to the Blocked state.

File Write

This event means that the thread is writing data to a file by using the write() method of the File class or its subclasses. This method can block the thread until the data is written or an exception occurs. This event corresponds to the Blocked state.

Profiling examples:

Example 1

Let’s assume we have a SpringBoot (embedded tomcat, default thread pool size = 200) service that exposes a GET /portfolio/{userId} endpoint to download a users portfolio. The controller forwards the request to the PortfolioDownloader class. This class allows only 5 threads to execute the critical section in parallel at any point in time using a Semaphore (i.e only 5 concurrent download requests). If we fire 200 concurrent download requests, what would be the state of the rest of the threads? Let’s profile and find out:

public enum PortfolioDownloader {

  INSTANCE;

  private static final Semaphore lock = new Semaphore(5);

  public String downloadPortfolio(String userId) {
    try {
      lock.acquire();
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    }
    System.out.println("Thread " + Thread.currentThread() + " downloading portfolio for user " + userId);
    //Mock download
    long aMockVariable = 0L;
    long downloadStartTime = System.currentTimeMillis();
    while (System.currentTimeMillis() - downloadStartTime < 2000) {
      aMockVariable++;
    }

    lock.release();
    return "s3://portfolioapp-" + aMockVariable + ".portfolio-" + userId;
  }

}
Enter fullscreen mode Exit fullscreen mode

Example 1 state changes

Example 1 logs

i.e Thread 5, 4, 7, 1, and 2 acquire the permit first and go about their task while Thread 23 (at the end) gets the permit after some time. This will also reflect in the Thread profiling page:

See how Thread 23 was parked for some time until it got the permit

Note — Threads call the Semaphore.accquire() method to get a permit. If a permit is not available, the method internally uses the LockSupport.park() to suspend threads until a permit is available!

Example 2

Let’s assume we have the following SpringBoot application:

@RestController
@RequestMapping("/v1/counter")
public class CounterResource {

  @PostMapping
  public ResponseEntity<Integer> increment() {
    return ResponseEntity.ok(Counter.increment());
  }

}
Enter fullscreen mode Exit fullscreen mode
public final class Counter {

  private static int COUNT = 0;

  public synchronized static int increment() {
    try {
      Thread.sleep(50);
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    }
    ++COUNT;
    System.out.println("Thread " + Thread.currentThread() + " called. Current count = " + COUNT);
    return COUNT;
  }

}
Enter fullscreen mode Exit fullscreen mode

We’ll fire 2087 concurrent requests to this API

Example 2 logs

Thread 14 acquires the monitor on the object first. See how Thread 68 goes into the Blocked State (Java Monitor Blocked) before it is able to acquire the monitor.

Example 2 profiling

Try to do thread profiling of a simple consumer producer (using wait() and notify()) application yourself using JFR and note the thread states!

Top comments (0)