The Threads

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.

💡
A thread group is a collection of threads, with the help of thread groups we can perform operations on multiple threads collectively.

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.

💡
Note: 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:

  1. 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
  1. 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();
💡
Since there are 2 ways to create a thread, which one should you choose? It comes down to personal preference, but as a general rule of thumb, you should use extends Thread if you intend to override other methods of the thread as well.
💡
Capable vs Enhanced -- When you make a class extend to a base class you are creating an enhanced version of the base class, If you create a class that implements an interface then you are creating a class which is capable of being the instance of the interface.

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:

  1. 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
  1. 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:

  1. Java Multithreading, Concurrency & Performance Optimization

  2. JAVA THE COMPLETE REFERENCE, 13E

  3. Ultimate Java Part 3: Advanced Topics