Java Thread Life Cycles
Ehis Edemakhiota
Posted on March 5, 2022
Introduction
A thread is the smallest unit of processing. A thread can be in different states in a cycle leading from its creation to its termination.
This article explores the different states in the Java thread life cycle. This is the second article of a concurrency series. The first article introduced the concepts of Multi-threading, Concurrency, Asynchronous Programming and Parallel Programming. You might want to take a look here.
Definition of Terms
- Process: A process is an instance of a program that is currently being executed by the processor. Different instances of the same program can run on a computer as different processes. This means that when I run MS Word on my computer, for instance, it runs as a process. I can run different instances of MS Word on my computer. For each new instance of MS Word, the processor spins up a new process. Each process has its own unique stack, memory and data.
- Thread: A thread is a subset of a process. A process starts with one thread called the main thread. The main thread can branch into other threads. A process that contains only the main thread is called a single-threaded process. A process that contains more than one thread is called a multi-threaded process. All threads within a process all share the same code-segment, data-segment, registers, stack and program counter.
Lifecycle of threads in Java
Java supports multithreading. A Java program can be designed in such a way that its different parts run on different threads. The execution of a Java program begins from the main
method. The main
method is executed on a special thread called the main thread, which is created by the Java Virtual Machine(JVM). Other threads are created as off-shoots of the main thread.
In Java, threads belong to the class- java.lang.Thread
. You can create a Java thread by implementing the Runnable
interface as shown in the following block:
/*
* CREATING A JAVA THREAD BY IMPLEMENTING THE RUNNABLE INTERFACE
*/
public class SimpleThreadTwo implements Runnable{
//the run method contains code that the thread will execute
@Override
public void run() {
System.out.println("A simple thread has been created by implementing the Runnable interface");
}
}
You can also create a Java thread by extending the Thread
class as shown in the following block:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/*
* CREATING A JAVA THREAD BY EXTENDING THE THREAD CLASS
* */
public class SimpleThread extends Thread {
// the run method contains code that the thread will execute
@Override
public void run() {
System.out.println("A simple thread has been created by extending the Thread class");
}
}
To execute both threads defined above, you create an ExecutorService. The ExecutorService
provides a thread pool that allows you to execute threads using the execute
method as shown below:
//creating an executor service that executes threads
class Executor{
public static void main(String[] args) {
SimpleThread simpleThread = new SimpleThread();
SimpleThreadTwo simpleThreadTwo = new SimpleThreadTwo();
// create the thread pool
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(simpleThread);
executorService.execute(simpleThreadTwo);
//the shutdown method notifies the executor service to stop accepting new tasks
//but the executor service still continues to run tasks that have already been accepted
executorService.shutdown();
}
}
Output:
A simple thread has be created by extending the Thread class
A simple thread has been created by implementing the Runnable interface
Process finished with exit code 0
You can also execute threads using the Thread
class by invoking the start
method. To execute a thread created by extending the Thread
class, you have to invoke the start
method on the thread object as follows:
Take note that you have to first create a class that extends java’s
Thread
class. (Using theSimpleThread
class created above)
public class MyThread {
public static void main(String[] args) {
SimpleThread simpleThread = new SimpleThread();
simpleThread.start();
}
}
Output:
A simple thread has be created by extending the Thread class
Process finished with exit code 0
To execute a thread created by implementing the Runnable interface, you have to pass in a Runnable
as an argument to the Thread
constructor. This is demonstrated in the code block below:
Take note that you have to first create a class that implements java’s
Runnable
class before you can do this.
(Using theSimpleThreadTwo
class created above.)
public class MyThread {
public static void main(String[] args) {
SimpleThreadTwo simpleThreadTwo = new SimpleThreadTwo();
//passing the Runnable object as an argument into the Thread class
Thread thread = new Thread(simpleThreadTwo);
thread.start();
}
}
Output:
A simple thread has been created by implementing the Runnable interface
Process finished with exit code 0
It is important to highlight that creating a thread by implementing the Runnable
interface is more encouraged than creating threads using the Thread
class.
The java.lang.Thread
class has a static enum property- State
. The State
property defines the potential state of a Java thread. During its life cycle, a java thread can belong to any of the following states:
- NEW
- RUNNABLE
- WAITING
- TIMED-WAITING
- BLOCKED
- TERMINATED
The following is a diagrammatic representation of the java thread lifecycle:
Source: baeldung.com
The following section describes these thread states in more detail:
-
NEW: When a thread is created, it is in the NEW state. A thread remains in the new state until its
start
method is called.
public class MyThread {
public static void main(String[] args) {
SimpleThreadTwo simpleThreadTwo = new SimpleThreadTwo();
//passing the Runnable object as an argument into the Thread constructor
Thread thread = new Thread(simpleThreadTwo);
System.out.printf("The current state of thread is %s", thread.getState());
}
}
Output:
The current state of thread is NEW
Process finished with exit code 0
-
RUNNABLE: A thread is in the RUNNABLE state when the processor executes its
start
method. Threads in the RUNNABLE state are either READY to run or are already RUNNING.
When a thread transitions to the RUNNABLE state, the thread starts first in the READY state. At the READY state, the thread has not been assigned processor time also called a quantum or a time slice. When a thread is assigned a time slice, the thread is said to be dispatched. A newly dispatched thread joins a queue of other dispatched threads that are also waiting to run. The operating system uses a Thread Scheduler to determine which thread to run.
When a thread starts to run, it transitions into its RUNNING state. In the RUNNING state, the task defined within the run
method of the threads is executed.
public class MyThread {
public static void main(String[] args) {
SimpleThreadTwo simpleThreadTwo = new SimpleThreadTwo();
//passing the Runnable object as an argument into the Thread constructor
Thread thread = new Thread(simpleThreadTwo);
// the start method executes the code defined in the run method of the thread
thread.start();
System.out.printf("%nThe current state of thread is %s%n", thread.getState());
}
}
-
WAITING: A thread enters the WAITING state while it waits for another thread to perform a task. A running thread can be placed on wait when one of the following methods is invoked:
-
wait
: The wait method causes an object to give up its timeslice and to release the monitor lock on a synchronized object. A monitor lock is a contract that gives a thread sole access to an object. The wait method causes the object to give up processor time and to transition from the RUNNING state to the WAITING state. A thread in waiting can transition back into execution when the thread that currently has the monitor lock calls thenotify
ornotifyAll
method. -
LockSupport.park
: TheLockSupport.park
method disables a thread unless a permit is available. A permit is a permission for a thread to continue execution. When thepark
method is called on the current thread, the thread remains in the WAITING state. When some other thread invokes theunpark
method with the parked thread as the target, the parked thread goes back into the RUNNING state. -
join
: Thejoin
method allows a thread to remain in the WAITING state until another thread completes execution.
-
public class WaitingStateDemo implements Runnable{
public static Thread firstThread;
public static void main(String[] args) {
firstThread = new Thread(new WaitingStateDemo());
firstThread.start();
}
@Override
public void run() {
Thread secondThread = new Thread(new WaitingStateDemoInner());
secondThread.start();
try {
secondThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static class WaitingStateDemoInner implements Runnable{
@Override
public void run() {
System.out.println("The current state of thread 2 is " + firstThread.getState());
}
}
}
Output:
The current state of thread 2 is WAITING
Process finished with exit code 0
Execution starts from the main method in the above program.
- First, we create
firstThread
and invoke itsrun
method by callingstart
. - In the
run
method offirstThread
, we createsecondThread
and start it. - While the processing of
firstThread
continues, we call thejoin
method onsecondThread
. Thejoin
method putsfirstThread
in the WAITING state untilsecondThread
has finished its execution. - Since
firstThread
is waiting forsecondThread
to complete, you can get the state offirstThread
fromsecondThread
.
To better understand the WAITING state, consider this scenario:
You have an onsite interview for a position that has the prospects of an amazing salary. On your arrival for the interview, the secretary tells you to wait until the hiring manager notifies you to come in. At this point, you are in the WAITING state. You cannot proceed to the interview until the hiring manager notifies you to come in.
-
TIMED-WAITING: A thread enters the TIMED-WAITING state when it is waiting for another thread to perform a task within a specified time interval. A thread can be put in the TIMED-WAITING state by calling one of the following methods:
- thread.sleep(long millis)
- thread.wait(int timeout) or thread.wait(int timeout, int nanos)
- thread.join(long millis)
- LockSupport.parkNanos
- LockSupport.parkUntil
You can demonstrate the TIMED-WAITING state by creating a thread from the main thread. You can call the sleep
method on the offshoot thread. This is shown as follows:
public class TimedWaitingStateDemo {
public static void main(String[] args) {
Thread newThread = new Thread(new DemoThread());
newThread.start();
// the following sleep gives the ThreadScheduler enough time to start processing newThread
try {
Thread.sleep(1000);
System.out.println("The current state of newThread is: " + newThread.getState());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println("Thread interrupted " + e);
}
}
}
class DemoThread implements Runnable{
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println("Thread interrupted " + e);
}
}
}
A thread in the TIMED-WAITING state transitions back to the RUNNABLE **state when the task it is waiting for completes execution or when the specified time interval elapses. To better understand the **TIMED-WAITING state, consider the following scenario:
You and a friend in another city get tickets to go see a music concert organized in your city. On the day of the concert, you agree with your friend to wait for him until 2 pm. If he does not arrive on/before 2 pm, you can go to the concert alone. Here, you are placed in the TIMED-WAITING state. You can choose to proceed with going to the concert after the specified time (until 2 pm) has elapsed.
-
BLOCKED: A thread transitions into the BLOCKED state when one of the following happens during program execution:
- When the thread is waiting for an I/O operation to complete.
- When the thread is waiting to gain access to the monitor lock on a synchronized object. A monitor lock is a contract that gives a thread sole access to a synchronized object. A synchronized object is an object that is designed to be accessed by only one thread at a time.
To understand the BLOCKED state, consider this scenario:
You have a meeting with a friend but as you step out of your house, it begins to rain. At this point, you are in a BLOCKED state. You are blocked from meeting with your friend until the rain stops.
-
TERMINATED: A thread enters the TERMINATED state ( sometimes called the DEAD state) when one of the following happens:
- when it completely executes its task ( as defined in the run method).
- when it terminates due to an error.
public class TerminatedStateDemo implements Runnable{
@Override
public void run() {
System.out.println("This demo is to show the terminated state");
}
public static void main(String[] args) throws InterruptedException {
Thread newThread = new Thread(new TerminatedStateDemo());
newThread.start();
// this sleep is intended to enable newThread execute completely
Thread.sleep(5000);
System.out.println("The current state of the new thread is : " + newThread.getState());
}
}
Output:
This demo is to show the terminated state
The current state of the new thread is: TERMINATED
Process finished with exit code 0
Thread Priorities and Thread Scheduling
The number of services assigned to a given thread is referred to as its priority. Every Java thread has a thread priority. The order in which threads are scheduled is determined by each thread’s thread-priority. Threads with a higher thread-priority are executed before threads with a lower thread priority. In Java, thread-priorities are scaled on a scale of 1 to 10.
- 1 is the lowest priority.
- 5 is the standard priority.
- 10 is the highest priority.
The priority of the main thread is set to 5 by default. Thread priority is a transferred property. Since every java thread is created off the main thread, every thread starts with a priority of 5. The priority of a thread can be programmed using the setPriority
method. The following enums represent thread priorities:
-
Thread.MIN_PRIORITY
represents the lowest priority. -
Thread.NORM_PRIORITY
represents the standard priority. -
Thread.MAX_PRIORITY
represents the highest priority.
Below is a program to understand the Thread-Priority:
public class ThreadPriority implements Runnable{
@Override
public void run() {
System.out.println("The thread priority of the running thread is " + Thread.currentThread().getPriority());
}
public static void main(String[] args) {
Thread firstThread = new Thread(new ThreadPriority());
Thread secondThread = new Thread(new ThreadPriority());
firstThread.setPriority(Thread.NORM_PRIORITY);
secondThread.setPriority(Thread.MAX_PRIORITY);
firstThread.start();
secondThread.start();
}
}
Output:
The thread priority of the running thread is 5
The thread priority of the running thread is 10
Process finished with exit code 0
It is important to note that thread priorities alone do not guarantee the order in which threads execute.
An Operating System’s Thread Scheduler determines the order with which threads run. A simple implementation of the Thread Scheduler ensures that threads with the same level of priority are assigned processor resources in a round-robin fashion. However, Thread Schedulers are not that simple. Concurrency is a complex concept and the JVM provides utilities that abstract these complexities from the programmer.
Starvation and DeadLock
Thread priorities determine the order in which threads are executed. Depending on the Operating System, a steady influx of higher priority threads could cause lower priority threads to stay in the waiting state indefinitely. Such indefinite postponement of thread execution is called starvation.
Closely, related to starvation is the concept of DeadLock. To explain Deadlock, let us say that there are two threads- Thread 1 and Thread 2. Thread 1 cannot execute because it is waiting (directly or indirectly) for Thread 2 to execute. Simultaneously, Thread 2 cannot execute because it is waiting for Thread 1 to execute. Both Thread 1 and Thread 2 are blocking each other, hence the term deadlock. The concept of deadlock concurrency is analogous to a traffic jam.
Conclusion
In this article, you learnt about the different states of Java threads, from time of creation to termination.
I hope that you enjoyed the article and learnt something new too. You can follow me on Twitter on @ehizmanhttps://twitter.com/ehizman_tutorEd. This will help me write more valuable content.
Special shout-outs to Confidence Okere, Oladimeji Omotosho, and Ozi Okoroafor for helping me with reviews and edits.
I look forward to hearing from you. Always code with ❤️
Posted on March 5, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.