Home

 › 

Articles

 › 

Multithreading In Java: Explained

Multithreading In Java

Multithreading In Java: Explained

Key Points

  • Multithreading in Java allows multiple tasks to run in parallel, improving system performance and resource efficiency.
  • Java’s Thread class and Runnable interface provide methods and constructors for creating and managing multithreaded programs.
  • A Java thread’s lifecycle consists of stages such as New, Runnable, Running, Waiting, and Dead.

In a world of complex and resource-demanding applications, any optimization made to save resources is very welcomed by programmers. Luckily, multithreading technology allows us to accomplish this very thing.

Java, along with other object-oriented programming languages, offers a variety of resource-efficient tools. Today we’ll be focusing on describing what multithreading is and how it works. We’ll be examining code examples, and we hope this will reveal a crucial facet of programming that will improve whatever project you are working on.

What is Multithreading?

When you use an application it runs on a processing unit. Usually, computers possess one or more processing units, allowing us to use multiple applications at the same time. 

But, to increase efficiency, programmers must find a way to use the power of just one processor to do many tasks. In Java, this is called multithreading and it’s achieved when two or more processing tasks run in parallel.

Multithreading can be useful to save on resources, and it can easily improve the performance of any system, particularly when our CPU isn’t all that powerful. 

But how does multithreading work? Programs can run sequentially by executing the code line by line, one after the other, in a single-thread fashion. But with multithreading, we can define multiple portions of code that when executed will start simultaneously. This is what the term “threads” refers to — parts of code that run in parallel. 

As programmers, we have another alternative called multiprocessing, that in opposition to multithreading, needs two or more CPU processors to run programs. These two are the major multitasking paradigms, and we’ll leave multiprocessing for another article. Let’s focus on this concept of “threads”.  

Java Threads Lifecycle

Threads are the foundation of any simple or complex computer program. They are a code unit that gets a task done, and once it’s finished commands what the next task is that the computer should process.

When we build programs using multiple threads, multiple virtual units are created, in a similar way to how a CPU can have multiple cores. One great advantage that multithreading has is derived from this fact: if errors occur in one thread, none of the others are affected. 

The “lifecycle” of a Java thread is the multiple states it takes during the running time of our code. 

We call the first stage “New”, and it occurs when a thread is created. It’s finished once we define a task for it. Then, the next stage is called “Runnable”. It happens once we define a task and the thread is ready to start working on it. The magic of multithreading happens precisely after a thread becomes runnable, as it’s after this point that it saves us resources.

After this, the “Running” stage begins when we give the thread the command to start and finishes precisely when the task is done. “Waiting” is a non-linear stage that occurs when our thread needs to wait for another thread to finish before it can continue processing its task.

Finally, the “Dead” stage is reached when the thread finishes the task and no further processing is necessary. 

An Example of Lifecycles in Java

This is what that can look like in a code example:

public class ThreadLifecycleDemo {
    public static void main(String[] args) {
        // Create a new thread
        Thread thread = new Thread(() -> {
            System.out.println("Thread is running.");

            try {
                // Simulate some work being done
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("Thread has completed its work.");
        });

        // Start the thread
        thread.start();

        // Wait for the thread to finish
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Main thread exiting.");
    }
}

Here, you see that a thread is created using a lambda expression. After defining it, the thread starts by printing that it is running to the console. Then it sleeps for two seconds and then prints that it has completed its work.

The “main” method starts the thread by calling the “start” method. This means that everything in the code will be executed simultaneously. After the thread is started, we call “join” so that we only print the final string after the thread has finished running. This is a precautionary measure more than anything else, and don’t worry about methods, as we’ll be talking about them next.

While this might seem like a cheeky example, we can build more complex applications upon it, as we’ll see in just a second.

An example of threads implemented in Java.

Thread Class and Methods 

Java is by far the best language to develop an understanding of multithreading because it widely supports this programming approach. It even provides many methods to make multithreaded programs as powerful as possible!

These methods, alongside with constructors, are available when we use the Java Thread class, which extends the Objects class and allows us to use the Runnable interface. Let’s see some of them. 

  • The start() and stop() methods initiate and terminate the thread’s execution.
  • The run() method executes a thread without creating a new one. 
  • The currentThread() method returns the thread we are currently located at. 
  • The isAlive() method allows us to know if a thread is alive (running) or dead (terminated), and the sleep() method temporarily suspends the thread. 
  • The yield() method allows us to tell the task scheduler to reorganize the thread’s execution order.
  • The suspend() method does the same, but we can specify an event that will restart our thread when it occurs. We can use the resume() method to resume the execution of a suspended thread immediately. 
  • The interrupt() method stops a thread that may be waiting for another action to happen. 
  • The destroy() method stops the execution of thread groups or subgroups. 

Multithreading can be quite difficult to understand practically, especially if you are a beginner programmer. But don’t worry! We have some examples for you in the next section, so keep reading.

Multithreading Syntax And Examples

We have two ways to create threads in Java, and each one is useful for different situations. The first method is using the Thread class which, as we saw before, offers a variety of methods and constructors. 

The second method means using the Runnable interface, whose instances are designed to be used by threads. It’s very useful when we want to use the same class in several threads simultaneously. Let’s look at an example:

class Multi extends Thread{
    public void run(){
        System.out.println("thread is running...");
    }
    public static void main(String args[]){
        Multi thread1=new Multi();
        thread1.start();
    }
}
A thread is implemented using the Thread class.

The above example shows how to create a new thread by extending the Thread class. The start() method is the first and most important method used. 

The method tells the computer that our thread is now Runnable when previously it was in the New state. When the task scheduler allows it, the run() method is commanded and the thread starts working on its task.

Now, let’s see an example that shows how to implement the Runnable interface. 

class Multi3 implements Runnable{
    public void run(){
        System.out.println("thread is running with runnable...");
    }
    public static void main(String args[]){
        Multi3 m1 = new Multi3();
        Thread thread1 = new Thread(m1);
        thread1.start();
    }
}

Once again, the output of this code will be:

An example of a thread implemented using Runnable interface.

As you see, there isn’t much complexity in the syntax once you understand the concept. However, you can keep these examples for future reference in case you ever need them. 

Multithreading in a Real-Life Situation

Here’s a more complex example of what multiple threads working simultaneously might look like in a real-world situation. Keep an eye out for the declarations of threads and the use of methods.

class MyThread extends Thread {
    @Override
    public void run() {
        try {
            for (int i = 1; i <= 5; i++) {
                System.out.println("Thread: " + Thread.currentThread().getId() + " - Value: " + i);
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class MultithreadingExample {
    public static void main(String[] args) {
        // Create and start multiple threads
        MyThread thread1 = new MyThread();
        MyThread thread2 = new MyThread();
        MyThread thread3 = new MyThread();
        
        thread1.start();
        thread2.start();
        thread3.start();
        
        // Wait for all threads to finish
        try {
            thread1.join();
            thread2.join();
            thread3.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("All threads have finished executing.");
    }
}
A more complex example of different threads implemented in the same program.

This is a simple program that will print a series of integers with a delay of 1 second between each iteration. We create three threads as instances of “mythread”, within the “main” method of the “MultithreadingExample” class. Then we use the run() method we mentioned earlier to initiate them.

Then, after the program has successfully printed all the numbers, the “join” method makes sure that the final message is only printed after every thread has reached its “dead” stage. This means that running this program will cause each thread to print its ID and a series of integers from 1 to 5 (you could of course specify others), with a 1-second delay between each value. Finally, the program will print “All threads have finished executing”.

Multithreading In Java: Explained

That’s about it: we’ve covered every fundamental concept necessary for you to understand what multithreading is and how to approach it. 

First, we went through the theory and reviewed the computer process that occurs behind the curtains. Also, we established the difference between multithreading and multiprocessing. 

Then, we learned about threads and what they mean in the multithreading paradigm. Moreover, we reviewed every stage that forms its lifecycle. Another important thing we talked about was multithreading methods and what they do. The list we provided should be enough for you to start experimenting on your own. 

Frequently Asked Questions

What is Java?

Designed in 1995, Java is an object-oriented programming language and one of the main advocates of this paradigm. Java’s great optimization of threads methods and constructors makes it a great choice for practicing multithread programming.

What is multithreading?

Multithreading refers to a programming feature that implies running two or more tasks simultaneously in a virtual instance, instead of a physical one. 

Is multithreading better than multiprocessing?

Multithreading and Multiprocessing are the two paradigms of computer multitasking. Neither one is better than the other, but we should choose which one we want to use depending on the characteristics of the code we’re working on.

How can we define multitasking?

In computer science, multitasking is the computer’s ability to perform more or two tasks by dividing its processing resources to accomplish the required tasks in the shortest possible time. 

What are thread methods?

Thread methods are built-in features that Java provides us to extend the functionality of our threads. We have methods to start, stop, suspend, and otherwise command our threads.

How many thread types are available in Java?

When multithreading in Java, we have User threads and Daemon threads. The first type is created by the programmer to do the work they need to accomplish. On the other hand, Daemon threads run in the background and do low-priority tasks like garbage collection.       

To top