The Thread States and Life Cycle in a Java Multithreaded Environment
Tags: java
Published: 2022-05-16 18:49:16

Before diving into the life cycle of threads in Java, let’s start with some basic concepts about this topic. A thread is a lightweight subprocess; it represents the smallest unit of processing, thus making it possible that multiple threads can use a shared memory area and run concurrently. Despite this, they are independent, meaning that in the case of an exception, one doesn’t affect the others. They exist inside a process, and this process can have multiple threads. At the same time, many processes can exist in the operative system. The task of running more than two threads simultaneously is called multithreading, and it maximizes CPU memory and saves time. 
The life cycle of threads consists of the following states:
* __New__: whenever a new thread is created, meaning that the code has not been run and the thread has not been executed yet, is called the new state. It is the very first state a thread can be in.
* __Active__: a thread can go from a new to an active state after the `start()` method is executed. This state can be split into two states:
     * __Runnable__: in a multithreaded environment like Java, several threads can be executed at the same time. Each one gets a fixed amount of time to run, and it’s the thread scheduler who provides that time and moves the thread to the running state. Once a thread is done running, it gives the CPU to another thread to run. All other threads that are waiting to run are said to be in a runnable state, which can be represented as a queue that contains all threads in order.
     * __Running__: it is the moment where a thread gets the CPU to be able to run. Threads come in order from the runnable state to the running state and generally go back to the runnable state after they are run.
* __Blocked/Waiting__: whenever a thread is temporarily inactive is either in the blocked state or waiting state. To explain these states, we need to talk about inter-thread communication and the concept of synchronization. In Java, there is this concept of a “monitor” (a synchronized block), which we can picture as a box that can only contain one thread at a time. When a thread has a lock on this block, it is in its running state. The rest of the threads that are waiting for a monitor lock are in the blocked state. Threads remain idle until the `wait()` method is called, placing them in a waiting state. In this state, the main thread is waiting for another thread to finish its execution, and once this happens, it then calls `notify()` so the main thread can acquire the monitor lock, and therefore, go to the active state.
* __Timed Waiting__: if a thread is being run and it enters a critical section of code that is not possible to leave, other threads can be waiting forever. This concept of eternal waiting is known as starvation, and to avoid this, a specific amount of time is given to each thread in the timed waiting state. For example, the `sleep()` method puts a thread in this state, and after the specific amount of time runs out, the thread wakes up. 
* __Terminated__: a thread moves to the terminated state (also called dead state) when it finishes its job. For example, when the `run()` method completes its execution, it would represent a normal termination. If any unusual event occurs, such as an unhandled exception or a segmentation fault, it is called an abnormal termination. Once a thread is killed, it cannot go back to any other state. 
Threads go from one state to another from the very beginning of the program execution. Java’s multithreaded environment allows multiple threads to be executed and moved between states, but since they share the same memory area, the CPU can be used by only one thread at a time. This thread will be the only one in a running state, while the rest of them will be in other different states. Since threads are lightweight subprocesses, they take little time to switch from one to another. This saves plenty of time when running a program, unlike process-based multitasking (known as multiprocessing), where each process has a different space in memory, is heavyweight, and switching between them can be time-consuming.