THREADS IN JAVA

What is a thread?

Thread is a flow of execution. The path that a process takes in order to finish its task. In java the main method that we use is a thread itself. It is called as the main thread. Whenever we launch a Java program using the keyword Java in the command prompt a new thread(Main thread) is created.

JVM (Java Virtual Machine) can support more than one thread at a time. If a java program is run using more than one thread it is called as a multi threaded application. Inside JVM there is a component called Thread Scheduler, it will take care of the execution of the threads. Now lets take a look into multi threading and its advantages.

What is multi threading?

Multiple Threads

If a thread means flow of execution. Multi threading means it is a flow of multiple executions(Multiple Tasks). Multi threading is used in every operating system now days, simply put take your pc or your phone. You might be listening to music while chatting with your friends while a movie or a game is downloading. All of these are separate processes that are done independently by one or more threads for each operations. But thread based multi threading will also have multiple threads that will act to complete a common task. If one of the thread is killed then the whole operation will also not complete.

Look at the following task a program must complete.

Read Customer Details
Store Customer Details on Database
Read Item Details
Store Item Details on Database

In the above example. There are 4 tasks; the first two are about customers and the other two are about items. These tasks are independent of each other. As these tasks are independent of each other, these tasks can be done parallelly. So a thread to process customer details and another thread to process item details will be able to increase this programs efficiency.

Just using multiple threads in a program does not mean that will increase the processing speed of the program. For example if there is a program that is used to read around 100 files and store that into the database. If we are to use only a single thread for this whole operation it might take around 60 minutes. Lets say we use around 100 threads. Each thread will read and write a file. It will bring down the process around 5 minutes. But using more than 100 threads will prove no use as there is no point in two threads processing the same file.

Note: The above mentioned example is only shown as a theoretical exam. The real time it may take to read and write may differ.

Thread Life Cycle

A thread will be in a multitude of states from its birth to its death. The cycle that it takes from the beginning to the end is known as the thread life cycle. These are the 7 main states that a thread will be in, during its life time.

Thread Life Cycle

1. New State -> The thread will be in this state when it is created.

2. Runnable -> The thread will be in this state when the start() method is called on the thread.

3. Running -> The thread will be in this state while it does its operations.

4. Waiting -> In this state, thread does not do any operation. It will simply wait indefinitely, until it is either interrupted or a specific thread finishes its operations.

5. Timed Waiting-> The thread will be in the waiting state until it times out or is interrupted.

6. Blocked -> The thread will be in this state until the lock is released by another thread or this thread is interrupted.

7. Terminated -> The thread will reach this state when it finishes its operations.

Threads will go back and forth between these runnable, running, timed waiting, waiting and blocked states. Once a thread reaches the dead state it cannot come back to the previous states.

Creating threads in Java

For a class to be recognized as a thread, that specific class must extend the Thread class or implement the Runnable method. Both of these options are good, not one is better than the other.

Lets take a scenario into consideration. We have a java class called Manager that extends Employee. In this instance we can not extend the Manager class with the Thread class as Java at this moment does not support multiple Inheritance. Instead we can Implement the Runnable class for Manager class to make it a thread supported class. The implementations of these will be explained in the following sections.

Extending the Thread Class

Alright, first lets see how to create a class by extending the Thread class.

Yes as you can see we have created the PrintOddNumbers class but in order for it to be recognized and work as a thread we have to override the run method. The job that a thread has to do goes inside the override run() method . Now in order to create this Thread’s object we will create an instance of this class in the main method.

Note: CurrentThread().getName() method will return the thread name for the thread that is doing the printing operation. Which can be easily seen from the output.

At line 9, Odd printer Thread object is initialized. (This thread is now in the New state.)

In order to run this thread we will have to call the method oddPrinter.start();

Now the thread will be in the running state. The thread will be executed and we will get an output that looks like this.

main  is printing
main is printing
main is printing
Thread-0 :1
Thread-0 :3
Thread-0 :5
Thread-0 :7
Thread-0 :9
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
Thread-0 :11
main is printing
main is printing
Thread-0 :13
Thread-0 :15
Thread-0 :17
Thread-0 :19
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing

The printer class started printing after main thread has started printing. It is because just because we started the thread before the main methods operations, it doesn't guarantee that the thread will immediately starts its operations. In other instances odd printer class might start and finish early as well.

Thread goes through other processes before it starts, the start method in the parent Thread class checks for various conditions and then only invokes the run method and starts a new thread. And also thread scheduler which is located inside the JVM decides which thread to run. As JVM is dependent on the JRE it will differ according to the operating system that is in use. Also main is the main thread and Thread-0 is the oddPrinter thread(child thread).

You might be thinking why invoke start method, when we can simply just call the run method directly. Lets do that and look at the output.

main :1
main :3
main :5
main :7
main :9
main :11
main :13
main :15
main :17
main :19
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing

As seen from the above info a new thread is not being created, instead the main thread is the only thread that is running. That is why after main is finished printing the odd numbers, it moves towards its own printing method. The reason for this is that the start() method takes care of the processes needed to create a new thread and then invokes the run method for the newly created thread to do its operations.

Now lets override the start() method and see what happens.

Odd Printer Thread is starting
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing

As we can see from the output a new thread is not being created. So the reason for this is the basic OOP concept. Whenever we invoke a method the JVM will check if that method is inside the child class, if it is not in the child it will go and check the parent. As we override the method, the method in the child will only be called. So lets call the super.start() method from the override method and see what happens.

Thread is starting
Thread-0 :1
Thread-0 :3
main is printing
main is printing
main is printing
main is printing
Thread-0 :5
main is printing
main is printing
Thread-0 :7
Thread-0 :9
Thread-0 :11
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
Thread-0 :13
Thread-0 :15
Thread-0 :17
Thread-0 :19

A new thread is started and it operates as it supposed to be.

Now lets say we overload the run() method, then what happens.

And the output will be this.

main  is printing
main is printing
main is printing
main is printing
main is printing
main is printing
Thread-0 :1
Thread-0 :3
Thread-0 :5
Thread-0 :7
Thread-0 :9
Thread-0 :11
main is printing
main is printing
Thread-0 :13
Thread-0 :15
Thread-0 :17
Thread-0 :19
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing

After we run our code we will only get the output from the run method without any parameters. The reason for this is the start() method. The start method that is located in the Thread class only runs the run method that does not contain any parameters.

Implementing the Runnable interface

Now lets create a thread using the runnable interface.

The Runnable is a SAM (Single Abstract Method)interface, it only has one method, which is the run() method. Now you may think if Runnable only has the run method, how do we start our thread. In order to start it as a thread we will create a Thread object passing our PrintOddnumbers object as a parameter.

As you can see we have created our thread object by parsing in our PrintOddnumbers instance. Then we can use the start() to run our thread as usual.

Note: Thread class has multiple different constructors to create a thread object. Click here to go to the official document to see the different constructors.

Thread Priority

Next lets take a look at thread priority and what it is. Thread priority means how early a thread is to be executed by the thread scheduler. I have mentioned above that JVM decides which thread to execute, but using the thread priority we can somewhat change that, the reason I am saying somewhat is because setting a high thread priority doesn't mean that we will get our desired output.

Priority will be on a range of 1 to 10, 1 being the lowest and 10 being the highest priority.

Thread.setPriority(int i)

Giving an integer value between 1 to 10 will set the thread priority for that thread.

Note: If a value that is not in the range is passed it will give a compilation error

A common misconception among programmers about thread priority is that the default priority is 5 for every thread. This is false information. Only main thread has the default priority of 5, every other thread will inherit the priority from the parent thread that is creating it.

Here we are printing the priorities in the parent thread and the child thread (getPriority())and we are passing in the integer value 9 as the priority of the main thread. After that we are creating the child thread(OddPrinter thread). In this instance the system will print these as their priorities.

Main :9
Odd Printer Thread:9

As seen from above the child thread is inheriting the thread priority from the parent thread.

Thread Join

As discussed in the start threads are used to run parallel operations. So some tasks might depend on the results of other tasks. Or some tasks may require more processing than other tasks. In these instances a thread can wait for another thread to finish before continuing its tasks.

Note: Using join method we need to catch the interrupted exception, as it may move the thread from waiting to runnable state

In this instance the main thread may finish its for loop block but once we call the join method it will forever wait (Waiting State)until odd printer finishes its operations. So the main thread will always finish after the child thread is finished. Join method also has two other overloaded methods.

join(long milliSecond)
join(long milliSecond,int nanoSecond)

If the above methods are used the thread that is calling the join function will only wait for the specified amount of time before continuing its operations.

In conclusion for a thread to get back to runnable state from waiting state there are 3 possibilities.

  1. The other thread(OddPrinter in this instance) finishes its task.
  2. The waiting thread times out.
  3. Thread interrupted due to interrupt() method.

Other Methods in Thread Class

There are other useful methods in the thread class that can be used for debugging.

yield()

The yield() method is a static method will be used on the thread that is calling this method. By using this method the thread tells the JVM scheduler to prioritize other threads over this.

for(int i=1;i<=20;i=i++){
if(i==10)
Thread.yield();
// THis thread will give importnce to other threads
//after printing 10 numbers.

System.out.println("Printer :"+i);
}

In this code block after printing to 10 the current thread will tell the Thread Scheduler to prioritize other threads over the current one. It does not mention a specific thread to prioritize, that will be chosen by the Thread Scheduler.

Note: This method should only be used for debugging and testing purposes. And this method is a native method.

sleep() and interrupt()

Sleep method will temporarily put the thread that is calling this method into the waiting state.

for(int i=1;i<=20;i++){
if(i==10){
try {
Thread.sleep(10000);
// Thread Waits for 10s before continuing
} catch (InterruptedException ex) {
//ExceptionHandling
}
}
System.out.println("Printer :"+i);
}

In the above example the thread will sleep for 10 second(Go into waiting state) after printing 10 numbers.

For a thread to go out of sleeping there are two possibilities.

  1. Sleeping time is finished.
  2. Thread is interrupted.

Now lets see the interput() method. interupt() method is called to a thread by another thread. If the interupt() method is used on a thread that is waiting due do wait(), sleep() or join() it will go from waiting state back to runnable state.

In this example the OddPrinter will wait for 10 seconds when it is started. But as we call the interupt() method on it from main it will start to run again as seen from the below output.

main  is printing
Odd Printer is Up
Thread-0 :1
Thread-0 :2
Thread-0 :3
Thread-0 :4
Thread-0 :5
main is printing
main is printing
Thread-0 :6
Thread-0 :7
Thread-0 :8
Thread-0 :9
Thread-0 :10
Thread-0 :11
Thread-0 :12
Thread-0 :13
Thread-0 :14
Thread-0 :15
Thread-0 :16
Thread-0 :17
Thread-0 :18
Thread-0 :19
Thread-0 :20
Printer thread is finished
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing
main is printing

Note: Calling interupt() on a thread that is not sleeping will not give any errors, instead the interupt() will kick in when the thread goes to waiting mode.

There is one more important part to cover about threads and that is thread synchronization. It will be covered separately in another article.

References

The video series done by Krishantha Dinesh is the source for most of this article.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store