Multithreaded Programming in Java

Java has a built-in support for multithreaded programming. This means that the Java Virtual Machine (JVM) allows our applications to have multiple threads running concurrently. Using this feature we can make our applications performance much better than it would have been if we created a single thread application.

In this article, we will cover the following:

  • What is a thread?
  • What is multithreading?
  • Multithreading in Java examples
  • Multithreading problems and solutions
  • Summarization

What is a thread?

A thread is a sequence of instructions executed within the context of a process. A thread is sometimes referred to as a lightweight process.

A common misconception is that a thread and a process is the same thing, or that a thread is a process. The misconception is common because a thread and a process are related to each other conceptually. A process and a thread both have a single sequential flow of control. The key difference that separates them is that processes don’t share memory while threads (within the same process) share a memory, which means threads are easier on resources.

What is multithreading?

Multithreading is when you run two or more threads concurrently. This is similar to how most operating systems can multitask by managing multiple processes at once. So multithreading is a form of multitasking.

With multithreading, the program does multiple tasks concurrently thus increasing the performance. The increased performance comes at a cost, which is that your applications complexity increases.

Why does the complexity increase? It increases because when we have threads that are sharing resources and running simultaneously, problems can occur. For example, a thread can change a variable while another thread is using it. This is obviously not good at all, so it needs to be handled, which increases the complexity.

Multithreading in Java examples

Let’s firstly see how we can create and start threads in Java before we start doing things with threads. It’s pretty straight forward, here you can see example code for creating and starting a thread that does nothing:

Thread myThread = new Thread();
myThread.start();

We want our threads to also do something. There are two main approaches to ensuring that our threads actually do something. The two main approaches are:

  1. Creating a subclass of the Thread class.
  2. Implementing the Runnable interface.

In both of the approaches, you will create a run() method. In both approaches, the main focus is on the run() method. The run() method contains all the work a thread should do. For simplicity sake, you can view it as the threads main() method. When the run() method stops, the thread will be terminated.

Extending Thread Class in Java example

Imagine that you had an application where you ask the user to input some value. This could be an excellent time to run another thread instead of just waiting for the user input.

First, we create a simple program that asks for an input string and prints it.

import java.util.Scanner;

public class Example {
    
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        String input = "";
        
        System.out.println("Input string: ");
        input = scan.nextLine();    
        
        System.out.println("Your input string was: " + input);
    }
    
}

We run it (I ran it in eclipse):

An input console program

Now we want to also at the same time have a thread (for whatever reason) to print out “Thread: come on give me the string!” every 5 seconds.

import java.util.Scanner;

class StressThread extends Thread {
    
    @Override
    public void run() {
        while(true) {
            System.out.println("Thread: come on give me a string!");
            try { this.sleep(5000); } catch(InterruptedException ie) {}
          }
    }
}


public class Example {
    
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        String input = "";
        
        StressThread t = new StressThread();
        
        System.out.println("Input string: ");
        t.start();
        input = scan.nextLine();    
        
        System.out.println("Your input string was: " + input);
    }
    
}

In the newly added StressThread, you can see that it extends Thread. We have also put what we want the thread to do inside the run() method. Now we will run it again, but wait 10 seconds before giving the input string, we should get 2 prints from our code before triggering the "Your input string was: " + input.

A thread console program

Great, it works. However, a problem that we now have is that our StressThread never stops. I had to manually stop the program. So how do you stop a thread? Thread.stop() might seem like the obvious solution, but it’s deprecated because it’s unsafe. You can read as to why here.

Instead of using the unsafe Thread.stop() method we can use a volatile variable in our class to ensure the run() method stops running which subsequently will kill our thread. We create our own Terminate() method which sets a boolean value to false.

import java.util.Scanner;

class StressThread extends Thread {
    private volatile boolean alive = true;
    
    public void terminate() {
        this.alive = false;
    }
    
    @Override
    public void run() {
        while(alive) {
            System.out.println("Thread: come on give me a string!");
            try { this.sleep(5000); } catch(InterruptedException ie) {}
          }
    }
}


public class Example {
    
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        String input = "";
        
        StressThread t = new StressThread();
        
        System.out.println("Input string: ");
        t.start();
        input = scan.nextLine();    
        
        System.out.println("Your input string was: " + input);
        
        t.terminate();
    }
    
}

This will now ensure so after we get the user input the thread stops and the entire program stops. We have now created a simple multithreaded program, you might think we only created one thread, but the main() method is a thread itself, it’s the main thread. Meaning, we used one child thread and the main thread in this example.

Implementing Runnable interface in Java example

If you don’t want to override the Thread methods you can instead use the Runnable interface. With this approach, you do not create your own thread, only the desired behaviour for a thread. We do this by passing our Runnable object as an argument when we create a thread. To achieve what we did with the subclassing approach but now with Runnable we could use the code below.

class MyRunnableStress implements Runnable {
    private volatile boolean alive = true;
    
    public void terminate() {
        this.alive = false;
    }
    
    @Override
    public void run() {
        while(alive) {
             System.out.println("Thread: come on give me a string!");
              try { Thread.sleep(5000); } catch(InterruptedException ie) {}
            }
        }
    }


public class Example {
    
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        String input = "";
        
        MyRunnableStress runnable = new MyRunnableStress();
        Thread thread = new Thread(runnable);
        
        System.out.println("Input string: ");
        thread.start();
        input = scan.nextLine();    
        
        System.out.println("Your input string was: " + input);
        
        runnable.terminate();
    }
    
}

Multithreading problems and solutions

Awesome, now you might perhaps have lots of ideas about how you can use threads to create cool applications. But before you go, I’d like to leave you with some pointers on problems that may occur, so you are not scratching your head when they do occur, because you will most certainly run into them. This is also where the idea of increased complexity is derived from regarding multithreading.

Synchronization

Threads in the same process share resources and because of this we need to be sure that they do not run over each other causing chaos. This can be done by using synchronization. We apply synchronization by using the keyword synchronized. The keyword can be used to create synchronized methods and synchronized statements.

A synchronized method, in essence, entails that a thread must wait to use the method of a specific object if another thread is already using it.

Deadlock

Deadlock can occur when we use synchronization. For example, imagine that the following scenario stepwise:

  1. A thread calls a synchronized method in object A.
  2. Another thread calls a synchronized method in object B.
  3. The first thread calls the synchronized method in object B. It starts waiting for the other thread to complete.
  4. The second thread calls the synchronized method in object A.
  5. Now both are waiting for each other to finish, forever.

The takeaway with this is to use synchronization sparingly, do not use it if it’s not needed. Also, ensure that the ordering of requests is in the correct order, so you avoid deadlock.

Thread Scheduling

When we have multiple threads we also need to schedule the order our threads should work in. Let’s say we have a server and we receive 5 requests all at once. Each request takes 3 seconds to process, which means that the person who made the 5th request will have to wait 15 seconds. The 5th user will probably believe that the request didn’t work and will move on to do something else.

By using a thread scheduler we can set different priorities for our threads, ensuring the right thread are prioritized. In this example, a well-planned prioritization could have made so the 5th users only had to wait 5 seconds instead of 15 seconds.

Summarization

Now after reading this article you should be able to start your multithread programming journey. Before you go, here’s a summarization list of some main points we covered:

  1. A thread is a sequence of instructions executed within the context of a process.
  2. Multithreading is when you run two or more threads concurrently.
  3. There are two main approaches to running threads which are creating a subclass of the Thread class or implementing the Runnable interface.
  4. Multithreading can drastically increase the performance of an application.
  5. Multithreading can increase the complexity of an application due to things like having to use synchronization, deadlock and thread scheduling.