Photo by Tim Mossholder on Unsplash

“Invisible threads are the strongest ties.” ― Friedrich Nietzsche

Before knowing about the threads in Java first we need to know about Multitasking.

The basic idea of multitasking is doing more than one thing at the same time. But can computers can do more than one task at a time? Actually, no, using techniques like timesharing in the OS kernel can switch between the tasks rapidly without even noticing. for this modern processors use “Cores” which is a processing unit inside the Central Processing Unit. However, computers with multiple processors can have a level of multitasking.

In computers, this multitasking can have two approaches is Process-Based and the other one is Thread Based.

What process-based multitasking is, it actually provides the ability to run multiple processes completely independent from one another, for example, a web browser running and at the same time a music player application playing music.

Thread-based multitasking actually consists of multiple threads, a thread is a small part of execution owned by a process, so one process can have multiple threads. These multiple threads are executing simultaneously. The “Cores” in the processors are responsible for making the best use of threads.

Not let’s find about threads in Java a detailed view.

What are Threads in Java?

As we discussed threads are execution paths. This can be a method or business logic involve inside the program. To run the Java, thread Thread Scheduler needs to allocate a portion of resources from the CPU, and the Java program is not the only program running on a computer so OS threads will get the priority too due to that reason predicting the execution order is impossible (if running as usually without any criteria).

Due to this uncertainty of predicting the order of execution programmers cannot assign a set of processes into individual threads and wait for the result because if there are any dependencies among them then the expected outcome will be not there.

Java's main method is also a thread, it is non-daemon to be exact. the start of the JVM begins with the start of the non-daemon thread and the JVM will exit at the end of the execution of the last non-daemon thread.

Thread Life Cycle

Like the processes running on OS, the thread also has its own states, based on the availability of time and priority these Threads have to change their states continuously.

NEW — Moment a new Thread created, but not yet started.

RUNNABLE — The thread is in the executing mode.

BLOCKED —The thread is blocked and waiting for the monitor lock to be released.

WAITING — The thread is waiting as long as it takes another thread’s signal.

TIMED_WAITING — The thread is waiting for a specific period of time

TERMINATED — The thread has exited.

These thread state transitions are not immediate. Thread Scheduler needs to allocate the CPU time based on availability.

Creating Threads

There are two main ways to create Threads in Java one is that to “implements” from the Runnable interface

public class RunnableThread implements Runnable {
@Override
public void run() {
//functionality to run during thread execution
}
}

Another way is to “extends” from the Thread class.

public class ExtendedThread extends Thread{
@Override
public void run() {
//functionality to run during thread execution
}
}

The way it looks both are the same, So what is the difference and what is the use of having two different ways of doing the same thing? Java is based on OOP principles, the classes we create should have a proper order of hierarchy.
As an example, if we are creating a Vehicle,

Vehicle -> Car -> SUV

A flow will be similar to like this, But in case If we want to add a Thread class, it will break the connections between the relative class, and also unlike languages like C+, Java does not provide multiple inheritances, in that case, there is no way of using more than one parent classes, At that time there we can use the interface.

Let’s see an example of creating threads in two different ways.

The above example of what it does is that it extends from the Thread class and overrides the run() method to add the functionality to run. Is it necessary to override? No! But the idea of creating a thread is to run some functionality, the responsibility of the run method is to hold that functionality, in case if it does not overrides the run() method it does nothing.

Now Let’s see how to run this,

Simply we create an object from the class, and call the method “start()”. Ok, why not run? We discussed that the responsibility of run is to hold the functionality to run(). But threading is a complex process the way it handles by communicating with the JVM, OS, creating a thread, adding them to a thread pool, all those should be handled. All those processes will be handled using the “start()” method.

Though we can call the “run()” method directly the program it will run without any error but it will not create a separate thread, It will run on the main thread.

Now Let’s see using the other way by using Runnable Interface.

Now things got started to change, unlike the Thread class when using the interface it is now enforcing the developer to override the “run()” method, otherwise it will lead to a compiler error.

When looking at the implementation the Runnable class is actually is Functional Interface (which contains only one abstract method). So now there is no way of inheriting the start() method, attributes, or other functionalities provided by the Thread class. Now how we run this. 🤔

Now again we have to use the Thread class as a wrapper class for the Instance (Object) for the class created using the Runnable interface. Here’s how we do it.

By wrapping using a thread Object, Now it can use all the methods necessary for Thread operations.

Java provides us the ability to override these methods without any restrictions, But this will affect the way how a thread should work. when a custom implementation is going on, in the functionality of a Thread class it is always better to reference its parent class implementation too, (ex — “super.start()”)

Priorities in Threads

The execution order of Threads are cannot be predicted as expected, But in Java Threads, it provided us to have a mechanism to assign priorities for the Threads one over another.

The thread priorities are value-based approach unlike the data structures it is not index-based. Java has defined ranges to define thread priorities, those are,

public static final int MIN_PRIORITY = 1;
public static final int NORM_PRIORITY = 5;
public static final int MAX_PRIORITY = 10;

But the values are not limited to these ranges they can be valued from 1 to 10 and less than or greater value other than this range cause “IllegalArgumentException”.

Priorities of the threads are inheritable, they are using the value of the main thread’s priority unless it’s not explicitly set value. Here’s an example,

Thread-Join

There may be scenarios where two or more different threads have to wait to do the rest of the process till other threads are finished. Java provided the method “join()” for these types of scenarios. The “Join” method has different implementations.

  • join() — This method waits until a specific method completes it works
  • join(long milliseconds) — This method waits until a certain period of
  • join(long milliseconds, int nanoseconds) — This method also waits for a certain period of time but a little bit of precise time

In the above example, it creates two Java threads one is counting from 1 to 10 and the other is 11 to 20. “threadTwohas to wait until it “threadOne ” completes, so the joining should be “threadOne.join()”.
The output will result like this,

Operations in Threads

Threads have varieties of other functionalities to work without causing collisions and with safety and allocating time. this is necessary if to keep the application flow smoothly. Java has few valuable operations that are.

“yield()” is a native static method in Thread class, it should deal with the native libraries to do its job.
What “Thread.yield()” does is at the moment the method is called it gives its timeslot for another thread to run.

@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i == 66){
Thread.yield();
//When counter reaches 66 give chance to another thread
}
System.out.println(i);
}
}

The “sleep()” method provides the ability to a thread wait for a certain time putting the thread into a TIMED_WAITING state, It has two ways of defining,

sleep(long miliseconds)sleep(long miliseconds, int nanoseonds)

And interrupt() method, wait till the method goes into sleep() and trigger the method to interrupt the sleep, but interrupting a non-sleeping method won’t throw an exception it just follows the usual path.

Software Engineer

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