Thread Life Cycle — Java

Rajat Gogna
The Startup
Published in
10 min readMay 24, 2020

All you need to know about java’s thread life cycle!

This write-up is aimed at explaining the various states of a thread in the Java world. If you are are new to the multithreaded programming realm, try my other blog here that explains thread fundamentals.

According to Sun microsystems, there are 4 states in the java thread life cycle. They are:

  • New — A thread is in the “New” state, when an object of the thread class is instantiated but the “start” method is not invoked.
  • Runnable — When the “start” method has been invoked on the thread object. In this state, the thread is either waiting for the scheduler to pick it up for execution or it’s already running. Let us call the state when the thread is already picked for execution, the “running” state.
  • Non-Runnable(Blocked , Timed-Waiting)— When the thread is alive, i.e., the thread class object exists, but it cannot be picked by the scheduler for execution. It is temporarily inactive.
  • Terminated — When the thread completes execution of its “run” method, it goes into the “terminated” state. At this stage, the task of the thread is completed.

Following is a diagrammatic representation of java thread lifecycle:

Fig 1 — Java Thread Life Cycle (Source — javatpoint)

Wait a second! Sounds cool, but what is this “scheduler” we are talking about? I called “start” so why doesn’t the system just start running my thread instead of waiting in the runnable state for the scheduler to pick it up?

That’s a good question! Scheduler is a software which is used to track computer’s tasks. It is responsible to assign tasks to resources which can complete the work. We won't go into depth about what logic the scheduler implements. For now, it’s enough to know that the scheduler has control over which task is to be assigned to which hardware resource and when, based on the eligibility of the resource and the state of the task.

Hmmm..Makes sense. I understand that the scheduler decides when to assign a task to the required resource, but how does the task enter the non-runnable state? How and why would a thread give up the processor time and pause execution? Is it by choice or is it forced?

You’re making me sweat now!!!

A thread can be in the non-runnable state due to various reasons; sometimes forced, sometimes by choice. Forced reasons could be that it is waiting for an I/O operation, like receiving a message on a port, or it could be waiting on an object, which is being held by another thread. The latter scenario results in case of a synchronized object. When a thread accesses a synchronized object, it creates a “lock” on that object. You can think of a lock as a temporary contract between the thread and the object, which gives the thread, sole access to the object, prohibiting any other thread from accessing it. Java associates a Monitor to every object to enforce this contract. A thread can also be moved to the non-runnable state (sleep) by the scheduler, based on the scheduler’s logic of resource sharing.

Threads can go to the non-runnable state by choice. Well, by the programer’s choice. The programmer can code the thread method (“run” or any other method which is called inside run) in such a way that it gives up CPU time deliberately. It is done to help get the most out of the available computing resources, or to induce delays after a certain part of the thread is executed. Let’s have a look at the methods available to us to give-up CPU time by choice:

  • sleep(long millis) — This method causes the calling thread to sleep for the duration provided as the parameter. It is important to note that on calling sleep, the thread gives up CPU time but does not release locks on objects. After the sleep duration, the thread moves back to the runnable state waiting for the scheduler to pick it up for execution. This is usually used to induce delay in part of the execution of the thread.
  • wait() or wait(long timeout) — This method causes the thread to give up CPU time as well as release any object locks. It may or may not be called with the timeout parameter. When called without timeout, the thread remains in the non-runnable state endlessly until another thread calls the notify() or notifyAll() method, and when called with the timeout parameter, it waits for at most the timeout duration, and then automatically moves to runnable state. This method is to be used in situations where multiple threads need to work in synchronicity.
  • yield() — This method is like a notification to the scheduler, that the thread is ready to give up execution. The scheduler then decides, based on other live threads and their priorities, if it wants to move the calling thread to runnable and give the CPU time to other threads, or keep running the existing thread. Personally, I find this method really useful. If we know that a method/function will take a lot of time to execute, and that work is not urgent, we can write it with strategically placed calls to the yield method, so that the scheduler can use the CPU for executing threads with higher priority and shorter execution time.
  • join() — join is called to pause the execution of the program until the thread calling the join method is terminated.

Time to get our hands dirty! Let’s write some code to create a couple of threads and inspect their state throughout the execution.

Simple Execution of a single thread.

The output of the above code is:

Fig 2 — Output of the code

Let’s try and understand what is happening here. The printThreadState function prints the current state of the thread. First, it is called after instantiating the thread object, and the output conforms with what we have learnt. The thread appeared to be in the “New” state. Observe the next output carefully. After the start method, we have executed the printThreadState method and the thread is in the “Runnable” state. This is before the scheduler has passed the thread for execution, because if the thread was already running, we would have got the print statement written inside the “run” method of Thread1 class, as output. Next output shows that the thread is being executed. Note that the thread is still in the “Runnable” state. This is because, as mentioned at the beginning of this article, Java only defines 4 states in the thread life cycle. The “running” state mentioned in this article is only for your understanding. Finally, after finishing execution of the run method, the thread is terminated and then destroyed. The for loops written in the program are only there to give the thread sufficient time to finish execution.

Section 2 — Multiple threads

1. Impact of Synchronization

Now, let’s have a look at the scenarios with synchronized blocks of code. We will write a program to run two threads trying to access the same instance of a class. We will run the program twice; once without the synchronized keyword in the method declaration, and once with the synchronized keyword.

Fig 3 shows the output without the synchronized keyword and Fig 4, with the synchronized keyword.

Fig 3 — Output for Non-Synchronized execution of the threads

Why is the output so random? 🤔

Its because the two threads are getting executed in parallel. Note the hold lock status of the threads clearly says false, therefore, any thread can access the object, as and when they get CPU time.

Fig 4— Output for Synchronized execution of the threads

Oh, now I get it. And since we have used the synchronized keyword to get the output in Fig 4, the first thread to get access to the object, held its lock. This BLOCKED the other thread from accessing the object until the first thread finished execution.

Exactly! You’re a quick learner.

Now, let’s quickly have a look a the usage of the methods that enable us to give-up CPU deliberately.

2. “sleep” and “yield” methods

Modify the code in the SynchronizationDemo.java and Persons.java files as shown in the above code snippet. Run it in both synchronized and non-synchronized modes as discussed earlier. We move the FirstThread to sleep mode for 2 seconds as soon as its execution starts.

Fig 5— Output for Non-Synchronized execution of the threads

As expected, in non-synchronous mode, both the threads don’t hold any lock on the object. FirstThread goes into Timed_Waiting on calling the sleep method. The SecondThread continues execution as there is no lock blocking its access to the object.

Fig 6— Output for Synchronized execution of the threads

In synchronized mode, the threads hold locks, and hence, even when the FirstThread is in the Timed_Waiting state, SecondThread is still in the Blocked state, and is unable to access the object.

Now, replace the sleep method with yield and remove the try-catch block in the Persons.java file. The output will be the same as in Section 2.1. This method’s usefulness lies in computationally expensive but less urgent tasks.

3. “wait” and “notify” methods

What if we do not want a thread to finish execution until some other thread tells it to continue?

Why would we want to do that?

Let’s say we have an account with a broadband service provider and we want to add more data balance to it. If we don’t have enough money in the account, we will not be able to recharge. So, the service provider will wait for us to add money to the account to complete the data recharge. Let’s have a look at the code.

Note that the thread methods are synchronized, therefore, only one thread can access the BroadBandAccount object at a time. The output of this code will depend upon which thread was picked up by the scheduler first. If the second thread, i.e., the add money thread is executed first, there will be enough money for the data recharge and the next thread in the object’s monitor, i.e., the add data thread, will not have to wait (go to the non-runnable state). However, if the add data thread is executed first, it will be moved to the non-runnable state because of insufficient balance and will wait for the add money thread to finish execution and call the notify method. What do you think happens if you don’t call the notify method in the latter scenario? Try for yourself 😉.

Fig 7 — Output of WaitiNotifyImplementationDemo.java

Also, did you notice the different style of instantiating the thread object? That is because we can only have a single run method in the class. I wanted to execute two different methods of the same class as threads, so I defined different run methods for both the threads dynamically.

4. “join” method

Join method is used when we want to stop execution of the program until the calling thread has finished execution. Can you think of such a scenario?

Let us consider the “Person” object’s example we used above. Let’s say the “Job” and “Address” were not simply string values but different objects. What if they had their own database tables, and their values had to be retrieved from there, in order to display the details of a person? We can have two threads create connections with each of the Job and Address tables, retrieve the records corresponding to the person, and then display the complete details. But we need to be careful here. If we retrieve these details using threads and then instantiate the person object with these values, we don’t have a guarantee that our threads would have retrieved the data by the time our object instantiation command executes. This is where join comes into picture. Have a look at the code below.

Output without join:
Person [name=Rajat, job=null, address=Dublin]
Output with join:
Person [name=Rajat, job=Software Developer, address=Dublin]

Observe the outputs in both the cases. I added the sleep method in the Job thread to increase the execution time in order to show the results better. Without the join commands, the execution did not wait for the threads to finish execution and created the Person object. Resulting in the object having null in the job attribute. Whereas, when we utilized the join method, all worked smoothly. Simply put, parallelising computationally intensive tasks reduces the overall execution time, but we should be careful not to lose any information in the process. Join method is one of the best tools for such tasks.

Aaaannnddd, that’s all for this blog. PHEW!

Over the years, doing multithreaded programming, finding the perfect way to implement things was a difficult task. So, through this blog I have tried to create a single knowledge-base to share the basic tricks of the trade. This, and my previous blog, together, aim to inculcate a sense of using multithreaded programming in the readers. I recommend playing around with the code from this blog to explore more, and develop a sense of understanding yourself.

All it takes, is PRACTICE…👍

Checkout my GitHub for the code of this blog, and other projects.

--

--