Understanding Threads in Java

Ramsunthar Sivasankar
Nerd For Tech
Published in
13 min readMay 15, 2021

--

Photo by Maria Ionova on UnsplashB

what is Thread??

It is a flow of execution through the process code and the process can contain multiple threads which is known as Multi-threading. Basically its a multiple flow of execution. So running multiple task at a certain time. This is where most of the people misunderstood, let’s say there is a process which runs for 10 minutes and there is no guarantee that creating ten threads for this process will cut down the time to one minute, because there maybe dependent tasks can be run in a program. But time can be cut down up to 7 or 8 seconds and it depends on the process. Threads used to perform complicated tasks in the background without disturbing the main process.

Multitasking

There are 2 type of multi-threading as shown in the above diagram. In Process based, lets say for example, programmers usually listen to music while coding and sometimes they may be downloading any documents from the internet. which means, they are doing multiple things on the same time. These tasks are independent, it won’t affect any of them if one of the above processes were killed (closing the program). In Thread based multitasking, each and every threads are belongs to a same main process. Usually a single main thread will be divided into multiple tasks. lets say there are 100 files to be processed for 100 minutes. By using two threads, these process might be finished in less than 100 minutes. But there is no way by increasing the number of threads will help to complete the task in 1 second or in no time. Because there is no point in taking a same file and processing again and again.This explanation is just for the clarification not the exact real life scenario and its just a basic idea of threads and their limitations.

Thread Life Cycle

(image:baeldung.com)

In the life cycle, there are 7 states. lets discuss about them,

New

This the state where the thread is created.

Ready/Runnable

When the start() method is invoked to a particular thread, it will switch state from New to Ready or Runnable state

Running

The Thread will switch to Running state when run() method is invoked. which means when the process is executing. But it may go back to Ready/Runnable state and come back to Running state and this can happen again and again also.

Blocked

This is the state where one thread blocked on the lock because other thread has already acquired this lock.

Waiting

In this state, the thread will wait forever until if there is any interruption. Usually the invocation of join() or wait() method will put the thread in waiting state.

Timed Waiting

When sleep() method or join() or wait() methods with timeout are invoked, that state is known as Timed Waiting. Name itself explains that the the thread will wait for a certain given time.

Dead

This state represents the completion of process.

how to create a Thread in Java ?

In Java, there are two ways to create a thread and they are,

  • By Extend Thread class
  • By Implement Runnable Interface

lets check it one by one,

Extending Thread class

In here I am going to use 2 classes. So to create a Thread, we need to extend the Printer class with the Thread class as shown in the code below.

Now to run the thread, we have to create an instance of the Thread class in the main method of the Application class and invoke that start() to that instance as I mentioned in the code below.

So If I run the application I will get the output as,

Main thread is running....
Child thread is running....

Facts about creating thread by Extending Thread class,

  • It is not must to override the run method in Thread class. Lets see why, when I call the start() it will check the Printer thread. Since I don’t have the start()method there, it will check the parent class witch is Thread class. In Thread class there will be a start() and that invokes run() method. So when calling run method it will the printer Thread class, since it doesn’t has the run() method, it will check the Thread class and it doesn’t do anything. Inside that run method, if the target is set then it will execute target.run() method. In our case we don’t have any target so it’s just don’t do anything. thats the reason it worked.
    So If we are not overriding the run method which means we are not doing any tasks, because all the process of threads have to go to run() method. This is only valid when we are Extend the Thread class. But when we implement the runnable interface, java program will force you to override the run() method. Because thats the behavior of the runnable interface which we are going to look next.
  • As you can see from the above code snippet, even though the start() method invoked(in line number 7 on the Application class) before printing the sentence in the main thread, it was printed after the main thread prints. which means, there is no guarantee that invoking start() method will immediately run the thread and it is entirely depend on the JRE(based on the OS). In the previous point, I mentioned about target which is a runnable object. In our case printer object, so if it has run() method then it will execute it and if not it will got the super class and execute the run() method as I said before.
    lets see some example,

so in this child thread this should print from 1 to 10.

and in the main thread this should print from 1 to 100

So I run this program several time to test the result. (I have taken only first few lines of the output to avoid too many unwanted spaces in the article)

1st time output

Main thread is running....
Child thread is running....
main 0
child 0
child 1
main 1
main 2
main 3
child 2
main 4
child 3
main 5
child 4
main 6
.
.

2nd time output

Main thread is running....
Child thread is running....
child 0
main 0
main 1
main 2
child 1
main 3
child 2
main 4
child 3
main 5
child 4
main 6
.
.

3rd time output

Main thread is running....
Child thread is running....
child 0
main 0
child 1
main 1
child 2
main 2
child 3
main 3
child 4
main 4
child 5
main 5
child 6
main 6
.
.

So from the above results, each execution has different orders, so this gave a conclusion, that there is no guarantee the thread will start immediate whenever the start() method is invoked. Its because of the Thread Scheduler and this decides which thread should run (order) and its entirely depends on JRE (OS based) as I mentioned above.

  • What will happen if we invoke run() method without the start() method ? lets find out,

So when I run the above program, I got the output as follows,

Main thread is running....
Child thread is running....
main main 0
child Thread-0 0
main main 1
child Thread-0 1
main main 2
child Thread-0 2
main main 3
child Thread-0 3
main main 4
child Thread-0 4
main main 5
child Thread-0 5
main main 6
main main 7
main main 8
main main 9
main main 10
child Thread-0 6
child Thread-0 7
child Thread-0 8
child Thread-0 9
main main 11
main main 12
main main 13
main main 14
main main 15
main main 16
main main 17
main main 18
main main 19
main main 20
.
.
.
main main 99

So as you can see from the above output, I got the thread names because in the code, I have invoked getName() for the current threads. So lets see,

as you can see I have removed the start method and used only run method and I got output as follows,

Child thread is running....
child main 0
child main 1
child main 2
child main 3
child main 4
child main 5
child main 6
child main 7
child main 8
child main 9
Main thread is running....
main main 0
main main 1
main main 2
main main 3
main main 4
main main 5
.
.
.
main main 99

So as you can the from the output, it execute the child before executing the main because the run() method is not giving chance to create a threat which means we only have one thread in this scenario. So as a result it execute as a normal method call. So start() method is necessary because whenever this method is called, the JVM will handle lot of things such as, check whether the thread is already exist or not, whether the thread is ready to run and then it will register on the registers and add it to the thread pool and finally it will invoke the run() method. So if we invoke run() method without start() method then it won’t be a multi-threading scenario.

  • What will happen if we override the start() method in the Thread class?
    Yes, but lets say we are invoking start() method in Printer class, what it does is, it will look for the immediate class and it has the method so it will run and it won’t go to the super class to create a thread. So is there any way to create the thread even overriding the start() method? answer is yes we can, by simply putting super.start() inside the start() method of the Printer class.
  • What will happen if we overload the run() method?
    Yes, but Thread classes’ start method always invoke with the no arguments.
  • Most of the programmers think that the java program will terminate once the main thread terminates. But in reality its not a daemon thread so the child thread can actually continue. what we can do is, we can change the printer thread object to a daemon thread by giving printer.setDaemon(true); in the main class. At this point when you run the program, when the main thread ends the child thread also suppose to end but you may notice that the child thread will run for a certain period of time even after the main thread ends because at the moment when the main thread printing the last line, the child thread already processed up to certain values and the delay is occurred because of the time taken to print.
  • The main disadvantage of this method is, we will loose the hierarchy of the classes when we extend Thread class to that particular class because Java does not support multiple inheritances.

Implement Runnable Interface

In the above code we have used implements Runnable instead of extending from Thread class.

Runnable interface is a SAM (Single Abstract Method) it has a single method called run and thats it. In this case we don’t have intermediate Thread class so we don’t have someone to implement the run() method. So what we did here is, We create an instance from a Thread class (which means we can pass Runnable instance to that as I mentioned in the previous thread creating method and also to give thread behavior) called thread and I pass the object printer as the parameter to the thread instance. Now printer is a Runnable class. That is why I invoked the start method from thread instance. And I got the output as follows,

Main thread is running....
Child thread is running....
main main 0
child 0
main main 1
child 1
main main 2
child 2
main main 3
child 3
child 4
child 5
main main 4
child 6
main main 5
child 7
main main 6
child 8
main main 7
child 9
main main 8
child 10
main main 9
child 11
Main thread Ends here
child 12
child 13
child 14
child 15
child 16
child 17
child 18
child 19
===========================

So this the method of creating thread in the second approach.

There are eight constructors in the Thread class such as,

  • Thread()
Thread T1 = new Thread();
  • Thread(Runnable target)
Thread T2 = new Thread(printer);
  • Thread(String name)
Thread T3 = new Thread(name:"printerThread");
  • Threat(Runnable target, String name)
Thread T4 = new Thread(printer, name:"printerThread");
  • Thread(ThreadGroup group, String name)
Thread T5 = new Thread(new ThreadGroup(),name:"printerThread");
  • Thread(ThreadGroup group, Runnable target)
Thread T6 = new Thread(new ThreadGroup(),printer);
  • Thread(ThreadGroup group, Runnable target, String name)
Thread T7 = new Thread(new ThreadGroup(),printer,name:"printerThread");
  • Thread(ThreadGroup group, Runnable target, String name, long stack size)

Thread Priority

Every thread has a thread priority and it will run according to the priority. and I to 10 is the Range of thread priority in Java. Basically 10 is the highest priority, 1 is the lowest and 5 is the normal priority.

Lets see how to set the Thread Priority to the previous example,

So to set the priority we have to invoke setPriority method to the thread. In our case the name of the thread is thread. So I invoked the method as in the above code (line number 9) and set it as a lowest priority thread.

Facts about the Thread Priority

  • Most of the programmers think that 5 is the default priority for every threads but that is not the case. Lets say there are two threads T1(main) and T2(child) and the thread priority is set to 5 for both threads. In reality there will be no changes. There is actually a rule in Thread Priority which is Main thread’s default priority value s 5 because it was created by the system. thereafter any thread that are created will inherit the parent thread priority value. So when creating the T1, the priority will be 5 and once we create T2 from T1, it will also take the same value even we didn’t set anything. But later we can set the priority by invoking the setPriority() method to the particular thread.
  • What will happen when we give priority value which are not in the range like 11 then what will happen ?
    I’ve tried to run a program by giving the thread priority to 11 and I got the output as follows,
Exception in thread "main" java.lang.IllegalArgumentException
at java.base/java.lang.Thread.setPriority(Thread.java:1137)
at threadSample.Application.main(Application.java:9)

as you can see I got an error saying IllegalArgumentException. So there is no chance it will automatically set to 10.

  • What will happen when we give priority value 1 and 10 for threads T1 and T2 respectively?
    most of the time there won’t be any big difference in the order of execution. Yes, the JVM will listen to the priority but we can’t be so sure that JVM will do accordingly. So if you want to see how it works, we have to implement this in the real project and there is way we can take the Thread dump and see. In there status of all threads will be recorded.

Other Methods in Thread

Join Method

Lets say there are two threads T1 and T2. T1 wants to wait for T2 to complete the task, then T1 should call the join method on T2 thread. We also can set time for T1 to wait. lets see what are those ways to call join method.

T2.join() — this will wait forever or until T2 dies

T2.join(long millis) —this will wait millis milliseconds for this thread to die.

T2.join(long millis,int nanos) — this will wait millis milliseconds plus nanos nanoseconds for this thread to die.

So whenever the join method is called, the thread will go to waiting state from Running state. In this example T1 thread will go to waiting state. Lets see on what case the T1 will go back to the running state,

  1. T2 complete its process
  2. Timeout (only if the time is set)
  3. When it is interrupted

Yield Method

When the yield() method is invoked, then it will send a hint to the scheduler that the current thread is willing to yield its current use of a processor. It is a native method because it not implemented in Java. Lets say there are three threads T1,T2 and T3. So once the T1 calls the yield() method, the scheduler will give chance to other threads and it is not sure that T2 or T3 give get the chance immediately. Lets say T2 is getting the chance and once it completes at that moment also we can’t say that the scheduler will give the chance to T1. Its totally depend on the process called Primitive Scheduling and if the platform doesn’t support this process so you won’t able to see these kind of execution.

Sleep Method

when this method is called, it can wait for a certain amount of time. In here there are two different approach to call this method such as,

  1. sleep(long millis) method which is a native method and simply we can give the sleep time in the parameter.
  2. sleep(long millis,int nanos) method which is not a native method(implemented in Java).

So if the sleep time finishes or if there is any interruption then the thread will go back to the running state.

Interrupt Method

When this method is invoked, the particular thread will comeback to the ready state from the waiting state. That is why when calling a sleep method we should include try catch method as follows,

try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

so to interrupt the thread we have to call the method as follows,

thread.interrupt();

In our example lets saythread is sleeping for 5000 ms and once we call the above method it will come back to the ready state. Important note here is one interrupt will only work for one sleep method. What if we call this method on the thread which is not sleeping. This will wait and execute the interrupt() method once the sleep() method is invoked until that it won’t do anything.

--

--

Ramsunthar Sivasankar
Nerd For Tech

MSc student of Greenwich University || Software Engineer