Thread lifecycle in Java

Simply speaking a thread goes from created to terminated state, passing by the execution phase. However in practice it's not so simple because some other steps can occur during execution

New ebook ๐Ÿ”ฅ

Learn 84 ways to solve common data engineering problems with cloud services.

๐Ÿ‘‰ I want my Early Access edition

This post covers the topic of threads lifecycle in Java. It's divided in 3 parts. The first talks about events happening when a thread is created. The second part explains what happens when created thread is executed. The next part lists some events that can suspend the execution. The last part describes the dead of the thread. Each part is exemplified by some tests showing presented states.

Thread creation

The first state of Java Thread is called NEW. It occurs when a new instance of Thread class is constructed and not started. Below code shows that:

@Test
public void should_put_thread_in_new_state() {
  Thread thread = new Thread();

  assertThat(thread.getState()).isEqualTo(Thread.State.NEW);
}

Threads can have different properties, such as for example priority. When a new Thread is created, it inherits the priority of its creator. The same rule applies for other attributes, such as thread type (deamon or not) and group (if SecurityManager is not set for the application). In additional, created thread has defined several properties describing it: name and id.

Thread execution

Created thread is nothing more than a simple supplementary object to keep in the heap. It does its job (= executes some code in separate process) only when start() method is invoked. The thread passes then from NEW to RUNNABLE state, as proven in below test case:

@Test
public void should_put_thread_in_runnable_state() {
  Thread thread = new Thread();
  thread.start();

  assertThat(thread.getState()).isEqualTo(Thread.State.RUNNABLE);
}

Physical thread execution is done by underlied OS and more particularly by its Thread Scheduler. This subject is too vast to be described in dozens of lines. It's detailed more in the post about OS Thread Scheduler and Java Thread. At this place it's enough to know that triggering thread execution relies on OS and is not implemented in the JVM.

Thread suspension

Sometimes a thread can be suspended. It occurs for example when it's waiting for other threads to do something. When it happens, the thread passes to one of suspension states: TIMED_WAITING, WAITING or BLOCKED.

The simplest to explain is TIMED_WAITING. It concerns a thread suspended with specific delay. It's typically the case of Thread.sleep(long) method. Other methods concerned by this state are: join(long), wait(long) of Object class and less known parkNanos and parkUntil of LockSupport. Below an example of thread in TIMED_WAITING state:

@Test
public void should_put_thread_in_timed_waiting_state() throws InterruptedException {
  CountDownLatch latch = new CountDownLatch(1);
  Thread thread = new Thread(() -> {
    try {
      Thread.sleep(3_000L);
    } catch (InterruptedException e) {
      e.printStackTrace();
      Thread.currentThread().interrupt();
    }
    latch.countDown();
  });
  thread.start();

  Thread.State state = thread.getState();
while (!latch.await(500, TimeUnit.MILLISECONDS)) {
      state = thread.getState();
  }

  assertThat(state).isEqualTo(Thread.State.TIMED_WAITING);
}

Thread goes to WAITING state when it waits for something. As the name suggests, this state is the response for wait(), join() (from Object class) and park() (from LockSupport class) methods. Thus, WAITING state represents the situation when waiting thread expects another thread to make a specific action. Below example shows that:

@Test
public void should_put_thread_in_waiting_state() throws InterruptedException {
  Thread.State[] thread1Waiting = new Thread.State[1];
  CountDownLatch latch = new CountDownLatch(2);
  Object barrier = new Object();
  Thread thread1 = new Thread(() -> {
    try {
      synchronized (barrier) {
        barrier.wait();
      }
    } catch (InterruptedException e) {
      e.printStackTrace();
      Thread.currentThread().interrupt();
    }
    latch.countDown();
  });
  Thread thread2 = new Thread(() -> {
    try {
      Thread.sleep(2_000L);
      synchronized (barrier) {
        // Only here thread1 is in 100% in WAITING
        // state. In other places some race conditions
        // could make impossible to catch this state.
        thread1Waiting[0] = thread1.getState();
        barrier.notifyAll();
      }
    } catch (InterruptedException e) {
      e.printStackTrace();
      Thread.currentThread().interrupt();
    }
      latch.countDown();
  });

  thread1.start();
  thread2.start();
  latch.await(3, TimeUnit.SECONDS);

  assertThat(thread1Waiting[0]).isEqualTo(Thread.State.WAITING);
}

The last suspension state, BLOCKED, has place when it tries to acquire monitor lock. It risks to happen when synchronized keyword appears, as in following example:

@Test
public void should_put_thread_in_blocked_state() throws InterruptedException {
  Thread thread1 = new Thread(() -> {
      test();
  });
  thread1.start();
  Thread thread2 = new Thread(() -> {
      test();
  });
  thread2.start();

  Thread.State thread1State = thread1.getState();
  Thread.State thread2State = thread2.getState();

  // One of 2 threads should be in TIMED_WAITING and the
  // other in BLOCKED state. TIMED_WAITING is explained
  // by the fact that synchronized method invokes a Thread.sleep(...).
  // BLOCKED state is because the thread can't execute until
  // the other thread releases the lock on synchronized block
  boolean isOneWaiting = thread1State == Thread.State.TIMED_WAITING
    || thread2State == Thread.State.TIMED_WAITING;
  assertThat(isOneWaiting).isTrue();
  boolean isOneBlocked = thread1State == Thread.State.BLOCKED
    || thread2State == Thread.State.BLOCKED;
  assertThat(isOneBlocked).isTrue();
}

private synchronized void test()   {
  try {
    Thread.sleep(5_000L);
  } catch (InterruptedException e) {
    e.printStackTrace();
    Thread.currentThread().interrupt();
  }
}

Thread can also blocks on some of I/O operations. But it's less the case with non-blocking IO (NIO) provided with Java 7.

Thread termination

The last step in thread lifecycle is naturally its termination represented by TERMINATED state. It occurs when thread ends its execution naturally or not (for example because of an exception). Below code shows both situations:

@Test
public void should_put_thread_in_terminated_state() throws InterruptedException {
  Thread thread = new Thread(() -> {});
  thread.start();

  // Only to give a time to JVM to
  // change the thread status
  Thread.sleep(200L);

  assertThat(thread.getState()).isEqualTo(Thread.State.TERMINATED);
}

@Test
public void should_put_thread_in_terminated_state_after_throwing_an_exception() throws InterruptedException {
  Thread thread = new Thread(() -> {
    while (true) {
      try {
        Thread.sleep(300L);
        throw new RuntimeException("Something went wrong");
      } catch (InterruptedException e) {
        e.printStackTrace();
        Thread.currentThread().interrupt();
      }
    }
  });
  thread.start();
  Thread.sleep(400L);

  assertThat(thread.getState()).isEqualTo(Thread.State.TERMINATED);
}

Note that thread can't be stopped manually. Even if stop() method exists, it's deprecated and shouldn't be used. It's deprecated because potentially stopped thread could lock different monitors. Sudden thread stopping could result in inconsistent state of locked objects and introduce by that an arbitrary behavior. To leave these object in consistent state, stop() was deprecated and another techniques to stop a thread defined.

This post covers different states of Java Thread. The first part explains what happens when it's created. It shows that it hasn't any effect on threads handled by OS. The second part talks about thread execution, when it becomes runnable. The 3rd part presents some situations when a thread can be suspended. It can occur for example when it waits for shared resource or other thread. The last part provides 2 situations for the final thread state - TERMINATED.