What Is Multithreading In Java?
In Java, multithreading refers to the process of running two or more threads at the same time to maximize CPU utilization. In Java, a thread is a lightweight process that uses fewer resources to create and share process resources. Multiple threading is built into the fabric of the Java language. It’s also simple to start a new execution thread:
Thread t = new Thread();
t.start();
That’s all. By creating a new Thread object, you’ve started a new thread of execution, complete with its own call stack.
Except for one problem.
Because that thread does nothing, it “dies” almost immediately after it is created. When a thread dies, its new stack vanishes. That concludes the story.
So, we’re missing one critical component—the thread’s job. In other words, we require the code that you wish to run in a separate thread.
Multiple threading in Java requires us to examine both the thread and the job that the thread is running. In addition, we’ll need to look at the Thread class in the java.lang package.
(Remember, java.lang is the package that is automatically imported, and it contains the most fundamental classes in the language, such as String and System).
Defining And Starting A Thread
An application that creates a Thread instance must supply the code that will run in that thread. There are two approaches to this:
Approach 1: You may provide a Runnable object to the Thread constructor(pass as an argument). The Runnable is an interface in Java. We will talk about it interface in a later tutorial. Runnable interface defines a single method, run(). The code that will be executed in our Thread must be put in the run() method. Look at the following RunnableDemo program:
class RunnableTask implements Runnable{
public void run() {
System.out.println("Hello from a thread!");
}
}
public class RunnableDemo{
public static void main(String args[]) {
RunnableTask rt1 = new RunnableTask();
Thread t = new Thread(rt1);
t.start();
}
}
Output
Hello from a thread!
Think of Runnable as the job and the Thread that is performing the job/task as the worker. When you create an object of type RunnableTask you are creating a job/task of RunnableTask type. And when you create a thread you are creating a worker. By passing the task as an argument to the constructor you are assigning the task to the worker.
Approach 2: Make a subclass of Thread. The Thread class itself implements the Runnable interface, but its run method does nothing. As in the ThreadDemo example, an application can subclass Thread and provide its own implementation of run:
public class ThreadDemo extends Thread {
public void run() {
System.out.println("Hello from a thread!");
}
public static void main(String args[]) {
Thread t = new ThreadDemo();
t.start();
}
}
Output
Hello from a thread!
Notice that both examples invoke Thread.start() in order to start the new thread.
Please pay special attention to the discussion that follows. Understanding Threads’ inner workings is critical to write efficient concurrent applications.
There are multiple threads in Java, but only one Thread class.
We can talk about thread with a lower-case ‘t’ and Thread with a capital ‘T’. Thread with a lower-case 't' and Thread with a capital 'T' are both acceptable. When we say thread, we're referring to a separate thread of execution. In other words, a distinct call stack. Consider the Java naming convention when you see Thread. What begins with a capital letter in Java? Interfaces and classes. Thread is a class in the java.lang package in this case. A Thread object represents an execution thread; each time you want to start a new thread of execution, you'll create an instance of class Thread.
A thread (lower-case ‘t’) is a separate thread of execution. This necessitates a separate call stack. Every Java application starts a main thread, which places the main() method at the bottom of the stack. The JVM is in charge of starting the main thread (as well as other threads as it sees fit, including the garbage collection thread). As a programmer, you can write code to start your own threads.
Thread (capital ‘T’) is a class that represents a thread of execution.
It includes methods for starting a thread, joining two threads, and putting a thread to sleep. (There are more methods; these are just the ones we need to use right now.)
What Does It Mean To Have “more than one” Call Stack?
With more than one call stack, it appears that multiple things are happening at the same time. Only a true multiprocessor system can do more than one thing at a time, but Java threads can make it appear as if you’re doing several things at once. In other words, execution can switch between stacks so quickly that it appears that all stacks are executing at the same time.Remember that Java is just a process that runs on your underlying operating system. So, first and foremost, Java must be ‘the currently executing process’ on the operating system. But, once Java has its turn to execute, what exactly does the JVM do? What bytecodes are executed? Whatever is currently at the top of the stack!
And the currently executing code may switch to a different method on a different stack in 100 milliseconds. A thread must keep track of which statement (of which method) is currently running on the thread’s stack.
It might look something like this:
The Thread class defines a number of thread management methods. These include static methods that provide information about the thread invoking the method or change its status. The other methods are invoked from other threads involved in managing the thread and Thread object. In the following sections, we’ll look at some of these methods.
But first, let’s talk about Thread a little more.
Every Thread requires a task to perform. A method to put on the new thread stack. Remember our first approach? It was necessary to provide a Runnable to the constructor in order to create a Thread object.
A Thread object requires a task. When the thread is started, it will perform a task. That job is actually the first method on the new thread’s stack, and it must always be something like this:
public void run() {
// code that will be run by the new thread
}
How does the thread know which method to stack at the bottom? Because a contract is defined by Runnable. Because Runnable is an interface. Any class that implements the Runnable interface can define a thread’s job. The thread only cares that you pass an object of a class that implements Runnable to the Thread constructor.
When you pass a Runnable to the constructor of a Thread, you’re really just giving the Thread access to the run() method. You’re giving the Thread a task.
In a nutshell, Implement the Runnable interface to create a task for your thread.
Now let us give this task to a Thread.
What do you think will happen if you run the RunnableDemo2 class? (In a few pages, we’ll find out).