The Main Thread
Every process has at least one thread called the main thread. Java provides us with the Threads
class to interact with threads. To get the current thread we can use a static method defined in the Thread
class called the .currentThread()
To prove that all programs contain a main thread let's take a simple Java program as an example:
public class Main {
public static void main(String[] args) {
System.out.println("Hello World");
System.out.println(Thread.currentThread());
}
}
// -- output
Hello World
Thread[main,5,main]
The current thread object in the above program has been printed on the terminal. The output Thread[main,5,main]
states that the current thread name is main
, the priority of the main thread is 5 and the name of the thread group where the main thread resides is also main
. We have discussed priorities in the Introduction to Multithreading article.
Controlling the [Main] Thread - Name & Priority.
We can control and configure any thread, but since we just only learned about the main thread, let us try to configure it. We can change the name of the thread using the .setName(String name)
method and priority using the .setPriority(int priority)
method. A name can be any name of our choice. A priority of the thread can be between 1 and 10, 1 being the lowest and 10 being the highest. We can also use constants defined in the Thread
class to set the priority i.e. Thread.MIN_PRIORITY
, Thread.MAX_PRIORITY
, Thread.NORM_PRIORITY
. The MIN_PRIORITY
has the value 1, MAX_PRIORITY
has the value 10 and NORM_PRIORITY
has the value 5. If priority is not provided then five is the default priority.
Thread.currentThread()
returns the object of type Thread
The .setName(String name)
and .setPriority(int priority)
are not static methods but rather instance methods defined on the thread object. Similar to the set, we can also get these properties using .getName()
and .getPriority()
method.
public static void main(String[] args) {
Thread currentThread = Thread.currentThread();
System.out.println("Thread name before: " + currentThread.getName());
System.out.println("Thread priority before: " + currentThread.getPriority());
currentThread.setName("The Main Main Thread");
currentThread.setPriority(10);
System.out.println();
System.out.println("Thread name after: " + currentThread.getName());
System.out.println("Thread priority after: " + currentThread.getPriority());
}
// --output
Thread name before: main
Thread priority before: 5
Thread name after: The Main Main Thread
Thread priority after: 10
Thread creation
We already know that Java provides us with the Thread
class to deal with threads. Hence to create threads in Java, we have to create an object of the Thread
class. We can create a thread object using two ways:
- Approach One -
extends Thread
Since inheritance represents an IS-A relationship. Hence, we can use inheritance to create a custom thread class.
public class MyCustomThread extends Thread { }
Only creating a custom thread class is of no use. To make use of this class we have to override the run
method in this class. The run()
method serves as the entry point of the thread execution.
public class MyCustomThread extends Thread {
@Override
public void run() {
System.out.println("Hello from " + Thread.currentThread().getName());
}
}
After class creation, we have to instantiate the MyCustomThread
class and start it. The start()
method on the thread object is used to start the thread.
public class Main {
public static void main(String[] args) {
MyCustomThread myCustomThread = new MyCustomThread(); // thread creation
myCustomThread.start(); // thread execution
}
}
// -- output
Hello from Thread-0
As we can see from the above output the name of the custom thread is Thread-0
. We can change this name using the setName(String name)
method. Note that you have to set the name of the thread before you start the thread.
myCustomThread.setName("My Custom Thread"); // set name
// -- output of the same program after addition of the above line before
// -- starting the thread
Hello from My Custom Thread
- Approach Two -
implements Runnable
Another way to create a thread is by using its constructor, There are many forms of its constructor the one which is used popularly is the new Thread (Runnable object)
. It accepts an object that implements the Runnable interface. The Runnable is an interface with only one abstract method i.e. run()
. The run()
method is where we can write the execution logic for the thread. We can provide an object which implements the Runnable interface in any of the 3 ways:
either by creating a class and making it implement the Runnable interface; or by using an anonymous inner class or by using Lambda expression, as Runnable is a functional interface. For the sake of simplicity, we will be using lambda expression.
public class Main {
public static void main(String[] args) {
Thread myAnotherCustomThread = new Thread(() -> {
System.out.println("Hello from " +
Thread.currentThread().getName());
});
myAnotherCustomThread.start();
}
}
// -- output
Hello from Thread-0
We can use another form of the constructor to directly provide a name to the thread.
Thread myAnotherCustomThread = new Thread(() -> {
System.out.println("Hello from " +
Thread.currentThread().getName());
}, "My Another Custom Thread");
myAnotherCustomThread.start();
extends Thread
if you intend to override other methods of the thread as well.Creating Multiple Threads
We can also create multiple threads and start them all together. For example, in the below code, we are creating a thread in the for loop
and adding it to a list of threads
. After all the threads
have been added we are again iterating over the list
and starting all the threads
one by one.
public class Main {
public static void main(String[] args) {
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 4; i++) {
Thread thread = new Thread(() -> {
System.out.println("Hello from: " +
Thread.currentThread().getName());
});
threads.add(thread);
}
for (Thread thread : threads) thread.start();
}
}
// -- output (order of the output may differ)
Hello from: Thread-0
Hello from: Thread-3
Hello from: Thread-1
Hello from: Thread-2
The sleep()
method
Remember that OS schedules different threads so that they receive a chunk of CPU resources and get executed? The sleep()
method is a static method declared in the Thread
class that makes the thread sleep for the specified time which is generally in milliseconds. Behind the scenes, it makes the CPU not schedule the thread until the specified time has elapsed. This method throws an InterruptedException
, which should be handled. This exception is thrown when any other thread sends an interrupt
signal to the thread while it is sleeping.
In the below code, we are simulating a process of downloading 4 files using the sleep method.
public static void main(String[] args) {
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 4; i++) {
Thread thread = new Thread(() -> {
try {
// wait for 1 second before proceeding further
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("Hello from: " +
Thread.currentThread().getName());
});
threads.add(thread);
}
for (Thread thread : threads) thread.start();
}
// -- output, after waiting 1 second. The output of the order may change.
Hello from: Thread-2
Hello from: Thread-0
Hello from: Thread-3
Hello from: Thread-1
On executing the above code, we get the mentioned output after waiting for approx 1 second. Note that all the lines of the output may be printed together (if your machine has 4 or more cores). To check the number of cores in your machine we can use the below code:
int numberOfCores = Runtime.getRuntime().availableProcessors();
System.out.println("Total no. of available cores are " + numberOfCores);
// -- output
Total no. of available cores are 8
Since I have 8 cores available in my machine that means I can run 8 threads in parallel (In ideal conditions).
The isAlive()
& join()
method
Threads run independently from each other and the order of their execution is out of our control. What would happen if one thread is dependent on the result of the other thread for its execution? One solution for this problem is busy waiting. In which thread A
which depends on the other thread B
for its execution, runs an empty while
loop to check if thread B
has been completed its execution. To do this we can use the isAlive()
method defined on the thread object. Tho this seems like a complete waste of resources still, we should explore its working.
Let's take a scenario, where we are downloading a file and then scanning it for viruses. For simplicity, we will be printing a log statement instead of downloading a file and scanning it.
public class Main {
public static void main(String[] args) {
Thread downloadThread = new Thread(() -> {
// logic for downloading the file
System.out.println("Downloading File");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("Download Complete");
});
Thread scanThread = new Thread(() -> {
// logic for scanning the file
System.out.println("Scanning File");
System.out.println("Scan Complete");
});
downloadThread.start();
scanThread.start();
}
}
// -- output
Downloading File
Scanning File
Scan Complete
Download Complete
As we can see the above code has started the scanning process and also completed it before the file can be downloaded. This is not the expected behaviour. We can use busy-wait to continuously check if the downloadThread
is still alive before starting the scanThread
.
downloadThread.start();
// an empty while loop to only check if the downloadThread has been terminated
// -- busy-wait
while (downloadThread.isAlive());
scanThread.start();
// -- output
Downloading File
Download Complete
Scanning File
Scan Complete
There can be many other scenarios where the isAlive()
method is of great use but this is not one of those scenarios. Using busy-wait here is a complete waste of resources.
The desired solution is that the dependent thread should go to sleep until the other thread has completed its execution and only after that it should wake up. We can achieve this by using the join()
method declared on the thread object. The join()
method waits until the thread on which it is called terminates. Its name comes from the concept of the calling thread waiting until the specified thread joins it. Let's take the same example as above that gives the following unexpected output.
// --output
Downloading File
Scanning File
Scan Complete
Download Complete
To solve this problem in an efficient way we can use the join()
method. We can call the join
method on the downloadThread
thread, after starting the downloadThread
but before starting the scanThread
.
downloadThread.start();
try {
downloadThread.join();
// -- the main thread waits here until the download thread is complete
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
scanThread.start();
// --output
Downloading File
Download Complete
Scanning File
Scan Complete
Note: Since the join()
method makes the calling thread go to sleep, hence the join()
method also throws an InterruptedException
that needs to be handled.
As a good practice, we should use another version of the join()
method that waits for the specified time before it can proceed further irrespective of the completion of the thread.
downloadThread.join(2000);
The Daemon threads
By default, the application will not stop until we have at least one thread running in it. Daemon threads are the threads that don’t prevent the application from exiting even if the main thread terminates. For example: any background thread that should not block our application from terminating. To mark a thread as a daemon thread we can use the setDaemon
property:
public class Main {
public static void main(String[] args) {
Thread downloadThread = new Thread(() -> {
// logic for downloading the file
System.out.println("Downloading File");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("Download Complete");
});
downloadThread.setDaemon(true);
downloadThread.start();
}
}
Thread Interruption
There are many cases in which we might have to terminate a thread or break out of the sleeping state of the threat or if the thread is misbehaving.
Interrupt Signal
We can terminate a thread by sending an interrupt signal to it i.e. By setting the interrupt flag of the thread to true. We can do this by using the interrupt method which is declared on the thread object. Note that just sending the interrupt signal is not enough it is solely on the thread on how it chooses to behave when an interrupt signal is received.
Hence, it makes sense to send an interrupt signal only in the following 2 cases:
- When a thread is executing a method that can throw an interruptedException on being interrupted. For example
thread.sleep()
public class Main {
public static void main(String[] args) {
Thread downloadThread = new Thread(() -> {
// logic for downloading the file
System.out.println("Downloading File");
// interrupt signal is being handled if the thread is asleep
try {
// waiting 10 secs before completing the thread
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("Download Complete");
});
downloadThread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// sending interrupt signal after waiting for a second
downloadThread.interrupt();
}
}
// -- output
Downloading File
Exception in thread "Thread-0" java.lang.RuntimeException: java.lang.InterruptedException: sleep interrupted
at io.rizwan.Main.lambda$main$0(Main.java:13)
at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.InterruptedException: sleep interrupted
at java.base/java.lang.Thread.sleep(Native Method)
at io.rizwan.Main.lambda$main$0(Main.java:11)
... 1 more
- If the interrupt signal is explicitly handled. To demonstrate this let's take a computation task that takes a long time to complete, Calculating an exponential power of a number. To explicitly check if the current thread is interrupted we can call the static method called
Thread.interrupted()
declared the Thread class.
import java.math.BigInteger;
public class Main {
public static void main(String[] args) {
BigInteger base = BigInteger.valueOf(200000);
BigInteger power = BigInteger.valueOf(100000);
Thread longComputationThread = new Thread(() -> {
System.out.println(base + "^" + power + " = " + pow(base, power));
});
longComputationThread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
longComputationThread.interrupt();
}
public static BigInteger pow(BigInteger base, BigInteger power) {
BigInteger result = BigInteger.ONE;
for (BigInteger i = BigInteger.ZERO;
i.compareTo(power) != 0;
i = i.add(BigInteger.ONE)) {
// checking continuously is the thread is interrupted
if (Thread.interrupted()) {
System.out.println("Prematurely interrupted computation");
return BigInteger.ZERO;
}
result = result.multiply(base);
}
return result;
}
}
// -- output
Prematurely interrupted computation
200000^100000 = 0
In the above code instead of using Thread.interrupted()
to check if the current thread is interrupted, we can also use Thread.currentThread().isInterrupted()
. The only difference between these 2 methods is that:
The Thread.interrupted()
method will change the status of the interrupt flag to false after returning it. So if the thread is interrupted and there are 2 calls to the Thread.interrupted()
method then the first call will return true whereas the second will return false.
The Thread.currentThread().isInterrupted()
will only return the status of the interrupt flag.
What's Next?
When we solve one problem, we create another. In the next post, we will be exploring what problem we have created by using multithreading.
References: