Threads in Java

Damsak Bandara
6 min readMay 8, 2021

--

Thread is basically a path to be followed when a program is in execution. All the Java applications contain at least 1 thread called the “Main Thread”. Threads help a Java program to do operations in a more efficient manner. Threads can be used to do multiple tasks at the same time(multitasking).

Multitasking can be divided into 2 main categories.

Process-Based multitasking: The operation system runs multiple processes at the same time.

Thread-Based multitasking: Two or more threads run concurrently.

Thread Life Cycle

These are the main steps of the thread life cycle. There is an additional step called the waiting state. That will be discussed later in the article.

Thread Creation

Consider the following process.

Assumption: For the calculation to happen the data should be prepared.

Can we create 5 threads and reduce the time for the above process?

No. There are dependency tasks. Likewise, we need to have a good understanding of the flow of the application before the creation of threads.

There are 2 main ways to create Threads in Java.

  1. Extending the Thread Class.
  2. Implementing the Runnable interface.

Thread creation by extending the Thread Class

The first step is to create the thread class.

Then in order to run the thread, we can create an object of this extended thread class and call the .start() method.

In this way of thread creation, it is not a must to override the run() method.

What happens internally?

  • First, it will check for any start methods in the “Printer” class.
  • If not it will execute the start method inside the parent “Thread” class.
  • Within the start method, there's an invocation to the run method.
  • Then it will check for any run methods inside the “Printer” class.
  • If there are no run methods inside the “Printer” class it will execute the inbuild run method inside the Thread class.
  • So it is not a must to implement the run method in this implementation.

The run method will execute the target.run() method. This target is a Runnable object.

What happens if we directly call the run method?

  • A new thread will not be created. It will just execute the normal method call.

What if we override the start method?

  • We can override the start method. However, a new thread will not be created.
  • In order to create a new thread inside an overridden method, we can use the “Super. start()”.

Can a Child thread still execute even after their main thread terminate?

Yes. child thread can keep executing even after the main thread terminates.

.setDaemon(true) — If we set a child thread to a Daemon thread, then that particular child thread will also terminate after the main thread terminates.

What is the problem with using this implementation?

Consider the following scenario,

  1. Human class
  2. Sportsman class that extends Human
  3. Runner class extends Sportsman

Assume that we want to create a thread in the Runner class. Can we do it?

Java does not support multiple inheritances. Therefore if we need to create a thread by extending the Thread class, we may need to break this inheritance. To overcome this problem we can use the Runnable interface to create threads.

Thread Creation by Implementing the Runnable Interface

The first step is to create the thread class using the Runnable interface.

Then in the main method, we need to create objects from both the Thread class and the implemented “PrinterRunnable” class. Then we need to pass the PrinterRunnable object to the Thread class.

The runnable interface just contains the abstract method run(). Therefore we need an object from the Thread class as well.

The constructors of the thread class —

  • Thread()
  • Thread(Runnable target)
  • Threat(Runnable target, String name)
  • Thread(String name)
  • Thread(ThreadGroup group, Runnable target)
  • Thread(ThreadGroup group, Runnable target, String name)
  • Thread(ThreadGroup group, Runnable target, String name, long stack size)
  • Thread(ThreadGroup group, String name)

Thread Priority

In Java, all the threads have a thread priority. This priority ranges from 1 to 10. 10 is the highest priority.

We can set the priority of a thread by calling the .setPriority(value) method.

Important point: Default Priority of the main thread is 5. Thereafter any thread you create gets inherited with the parent thread’s priority value.

Example:

Assume the main thread creates thread t1. Then thread t1 gets the main thread’s priority value. Now we change the priority of the t1 thread and create another thread called t2. Now, t2’s priority is 7.

What if we set the priority of the thread above 10?

Then the application will throw an illegal argument exception.

What if 2 threads carry the same priority value?

The scheduler will arbitrarily choose one to run.

Thread scheduler

This is the part of the JVM that decides which thread should run. However, there is no guarantee.

Primitive scheduling: The highest priority task executes until it comes to the waiting state or dead state. If another higher priority task comes into the scheduler it will execute.

Time Slicing: The given task will execute for the predefined period and then go to the pool of ready tasks.

Thread join ( .join() )

Allows one thread to wait until another completes its execution. There are 3 main overloading functions.

  1. join(): Will put the current thread on the waiting state until the thread on which it is called is dead or interrupted.
  2. join(long milliseconds): Will put the current thread on the waiting state until the thread on which it is called is dead or exceeds the specified time limit.
  3. join(long milliseconds, int nanoseconds): Will put the current thread on the waiting state until the thread on which it is called is dead or exceeds the specified time limit.

Waiting State of a thread

A thread may go to the waiting state after the .join() method is called.

Rule: If any thread goes to the waiting state, there is no way for it to go back to the Running state directly. It has to go to the Ready state and then to the Running state.

Situations where a thread may go to the ready state from the waiting state.

  • Called thread completes its job.
  • Timeout occurs.
  • Interruption occurs.

Thread Yield ( yield() )

This method is used to inform the scheduler that the currently executing thread is willing to withdraw its current use of the processor but like to be scheduled back as soon as possible.

Yield is a Native method(Not implemented in Java). Used to perform debugging.

  • If the scheduler decides to give the chance to another process, then this particular thread which called the yield method will go to the waiting state.
  • However, there is no guarantee that the previously executed thread will get the chance again.

yield() vs wait()

Thread Sleep ( sleep() )

This method is used to pause the execution of a thread for a specified period of time. The thread will go to the waiting state for a particular amount of time.

Contains 2 method signatures.

  1. .sleep(long miliseconds) : Native method.
  2. .sleep(long miliseconds, int nanoseconds) : Non-Native method.

Thread Interrupt(interrupt())

This method is used to interrupt a thread that is in the sleeping or waiting for the state. When we use this method we have to do to necessary steps to catch the interrupt exception as well.

1 interrupt() call will only work for 1 sleep.

What happens if we call interrupt() on a thread which is not sleeping?

  • Then the interrupt will wait for any time where the thread will actually go to wait or sleep and then execute it(make the Thread come back).

References

--

--