Java Multithreading Tutorial for Beginners

As we know that Multithreading in Java is a very important topic. Multithreading means doing things simultaneously, in parallel. In Java, concurrency is done with threads. 

Threads are units of code that can be executed at the same time. They are sometimes called lightweight processes, although, in fact, a thread is executed within a process (and every process has, at least, one thread, the main thread).

Video

The tutorial explained in below youtube video:

What is Thread?

The life cycle of a Thread (Thread States)

A java thread can be in any of the following thread states during its life cycle i.e. New, Runnable, Blocked, Waiting, Timed Waiting or Terminated. These are also called life cycle events of a thread in java. Let’s understand each state in more detail.

1) New

The thread is in the new state if you create an instance of Thread class but before the invocation of start() method.

2) Runnable

The thread is in the runnable state after the invocation of the start() method, but the thread scheduler has not selected it to be the running thread.

3) Running

The thread is in running state if the thread scheduler has selected it.

4) Non-Runnable (Blocked)

This is the state when the thread is still alive but is currently not eligible to run.

5) Terminated

A thread is in a terminated or dead state when its run() method exits.

Defining and Starting a Thread

An application that creates an instance of Thread must provide the code that will run in that thread. Java provides two ways to create a thread programmatically.
1. Implementing the Runnable interface.
2. Extending the Thread class.

1. Implementing the Runnable interface

The Runnable interface defines a single method, run(), meant to contain the code executed in the thread. The Runnable object is passed to the Thread constructor.
Let's create JavaThread class which implements Runnable interface:
package com.java.multithreading.runnable;

public class JavaThread implements Runnable {

    @Override
    public void run() {

        System.out.println("Starting thread: " + Thread.currentThread().getName());
        // 2000 milliseconds = 2 seconds
        for (int i = 0; i < 10; i++) {
            System.out.println(" Java programming ..");
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        System.out.println("Completed thread: " + Thread.currentThread().getName());
    }
}
Let's create PythonThread class which implements Runnable interface:
package com.java.multithreading.runnable;

public class PythonThread implements Runnable {

 @Override
 public void run() {
  System.out.println("Starting thread: " + Thread.currentThread().getName());
  // 2000 milliseconds = 2 seconds
  for (int i = 0; i < 10; i++) {
   System.out.println(" Python programming ..");
   try {
    Thread.sleep(200);
   } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }
  }
  System.out.println("Completed thread: " + Thread.currentThread().getName());
 }

}
Let's create CThread class which implements Runnable interface:
package com.java.multithreading.runnable;

public class CThread extends JavaThread implements Runnable {

    @Override
    public void run() {
        System.out.println("Starting thread: " + Thread.currentThread().getName());
        // 2000 milliseconds = 2 seconds
        for (int i = 0; i < 10; i++) {
            System.out.println(" C programming ..");
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        System.out.println("Completed thread: " + Thread.currentThread().getName());
    }
}
For creating a new thread, create an instance of the class that implements the Runnable interface and then pass that instance to Thread(Runnable target) constructor.
package com.java.multithreading.runnable;

public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        new Thread(new JavaThread(), "Java Thread").start();
        new Thread(new JavaThread(), "Python Thread").start();
        new Thread(new JavaThread(), "C Thread").start();
    }
}
Let's run the above program and below is output:
Starting thread: Java Thread
 Java programming ..
Starting thread: Python Thread
 Java programming ..
Starting thread: C Thread
 Java programming ..
 Java programming ..
 Java programming ..
 Java programming ..
 Java programming ..
 Java programming ..
 Java programming ..
 Java programming ..
 Java programming ..
 Java programming ..
 Java programming ..
 Java programming ..
 Java programming ..
 Java programming ..
 Java programming ..
 Java programming ..
 Java programming ..
 Java programming ..
 Java programming ..
 Java programming ..
 Java programming ..
 Java programming ..
 Java programming ..
 Java programming ..
 Java programming ..
 Java programming ..
 Java programming ..
 Java programming ..
Completed thread: C Thread
Completed thread: Python Thread
Completed thread: Java Thread

Runnable Interface with Lambda Expressions

As we know the Runnable interface is a functional interface so we can use Lambda expression to implement the Runnable interface. The above example can be made even shorter by using Java 8’s lambda expression:
package com.java.multithreading.runnable;

public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {

        new Thread(() - > {
            System.out.println("Starting thread: " + Thread.currentThread().getName());
            // 2000 milliseconds = 2 seconds
            for (int i = 0; i < 10; i++) {
                System.out.println(" Java programming ..");
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            System.out.println("Completed thread: " + Thread.currentThread().getName());
        }, "Java thread").start();

        new Thread(() - > {
            System.out.println("Starting thread: " + Thread.currentThread().getName());
            // 2000 milliseconds = 2 seconds
            for (int i = 0; i < 10; i++) {
                System.out.println(" Python programming ..");
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            System.out.println("Completed thread: " + Thread.currentThread().getName());
        }, "Python thread").start();

        new Thread(() - > {
            System.out.println("Starting thread: " + Thread.currentThread().getName());
            // 2000 milliseconds = 2 seconds
            for (int i = 0; i < 10; i++) {
                System.out.println(" C programming ..");
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            System.out.println("Completed thread: " + Thread.currentThread().getName());
        }, "C thread").start();
    }
}
Output:
Starting thread: Java thread
 Java programming ..
Starting thread: Python thread
 Python programming ..
Starting thread: C thread
 C programming ..
 Java programming ..
 C programming ..
 Python programming ..
 Java programming ..
 C programming ..
 Python programming ..
 Java programming ..
 C programming ..
 Python programming ..
 Java programming ..
 Python programming ..
 C programming ..
 Java programming ..
 Python programming ..
 C programming ..
 Java programming ..
 Python programming ..
 C programming ..
 Java programming ..
 Python programming ..
 C programming ..
 Java programming ..
 Python programming ..
 C programming ..
 Java programming ..
 Python programming ..
 C programming ..
Completed thread: Java thread
Completed thread: Python thread
Completed thread: C thread
Learn Lambda expressions at https://www.javaguides.net/2018/07/java-8-lambda-expressions.html.
Learn functional interfaces at https://www.javaguides.net/2018/07/java-8-functional-interfaces.html.

2. Extending the Thread class

You can create a new thread simply by extending your class from java.lang.Thread and overriding it’s run() method. The run() method contains the code that is executed inside the new thread. Once a thread is created, you can start it by calling the start() method. 
Let's demonstrate how to create and start a Thread by extending java.lang.Thread class. We will create multiple threads and see the result.
Let's create JavaThread class which extends Thread class:
package com.java.multithreading.thread;

public class JavaThread extends Thread {

    @Override
    public void run() {

        System.out.println("Starting thread: " + Thread.currentThread().getName());
        // 2000 milliseconds = 2 seconds
        for (int i = 0; i < 10; i++) {
            System.out.println(" Java programming ..");
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        System.out.println("Completed thread: " + Thread.currentThread().getName());
    }

}
Let's create PythonThread class which extends Thread class:
package com.java.multithreading.thread;

public class PythonThread extends Thread {

    @Override
    public void run() {
        System.out.println("Starting thread: " + Thread.currentThread().getName());
        // 2000 milliseconds = 2 seconds
        for (int i = 0; i < 10; i++) {
            System.out.println(" Python programming ..");
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        System.out.println("Completed thread: " + Thread.currentThread().getName());
    }
}
Let's create CThread class which extends Thread class:
package com.java.multithreading.thread;

public class CThread extends Thread {

    @Override
    public void run() {
        System.out.println("Starting thread: " + Thread.currentThread().getName());
        // 2000 milliseconds = 2 seconds
        for (int i = 0; i < 10; i++) {
            System.out.println(" C programming ..");
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        System.out.println("Completed thread: " + Thread.currentThread().getName());
    }
}
Let's create multiple threads and start them using the start() method:
package com.java.multithreading.thread;

public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {

        System.out.println(" Main thread name => " + Thread.currentThread().getName());

        Thread thread = new JavaThread();
        thread.setName("Java Thread");
        thread.start();

        Thread pyThread = new PythonThread();
        pyThread.setName("Python Thread");
        pyThread.start();

        Thread cThread = new CThread();
        cThread.setName(" C Thread");
        cThread.start();
    }
}
Output:
 Main thread name => main
Starting thread: Java Thread
 Java programming ..
Starting thread: Python Thread
 Python programming ..
Starting thread:  C Thread
 C programming ..
 Python programming ..
 Java programming ..
 C programming ..
 Java programming ..
 Python programming ..
 C programming ..
 Python programming ..
 Java programming ..
 C programming ..
 Java programming ..
 Python programming ..
 C programming ..
 Java programming ..
 Python programming ..
 C programming ..
 Java programming ..
 Python programming ..
 C programming ..
 Java programming ..
 Python programming ..
 C programming ..
 Python programming ..
 Java programming ..
 C programming ..
 Python programming ..
 Java programming ..
 C programming ..
Completed thread: Python Thread
Completed thread: Java Thread
Completed thread:  C Thread

Thread.sleep() Method Example

In this example, we have created and started two threads thread1 and thread2. note that we have used both overloaded versions of sleep() methods in this example.
Thread.sleep(1000);
Thread.sleep(1000, 500);
/**
 * thread sleep method examples
 * @author Ramesh fadatare
 *
 */
public class ThreadSleepExample {
    public static void main(final String[] args) {
        System.out.println("Thread main started");
        final Thread thread1 = new Thread(new WorkerThread());
        thread1.setName("WorkerThread 1");
        final Thread thread2 = new Thread(new WorkerThread());
        thread1.setName("WorkerThread 2");
        thread1.start();
        thread2.start();
        System.out.println("Thread main ended");
    }
}

class WorkerThread implements Runnable {

  @Override
  public void run() {
     for (int i = 0; i < 5; i++) {
         try {
              Thread.sleep(1000);
              Thread.sleep(1000, 500);
              System.out.println("[" + Thread.currentThread().getName() + "] Message " + i);
         } catch (final InterruptedException e) {
              e.printStackTrace();
         }
      }
   }
}
Output:
Thread main started
Thread main ended
[WorkerThread 2] Message 0
[Thread-1] Message 0
[WorkerThread 2] Message 1
[Thread-1] Message 1
[WorkerThread 2] Message 2
[Thread-1] Message 2
[WorkerThread 2] Message 3
[Thread-1] Message 3
[WorkerThread 2] Message 4
[Thread-1] Message 4
Note that sleep() method throws InterruptedException exception when another thread interrupts the current thread while sleep is active.

Runnable or Thread, Which one to use?

The second method, where you create a thread by extending from Thread class is very limited because once you extend your class from Thread, you cannot extend from any other class since Java doesn’t allow multiple inheritance.
Also, If you follow good design practice, Inheritance is meant for extending the functionality of the parent class, but when you create a thread, you don’t extend the functionality of Thread class, you merely provide the implementation of a run() method.
So, In general, You should always use a Runnable object to create a thread. This method is more flexible. It allows your class to extend from any other class. Also, you can use anonymous class syntax and Java 8’s lambda expression with Runnable to make your code more concise.

Further Readings

>> Java Thread Join Example

Sometimes we need to wait for other threads to finish it’s execution before we can proceed. We can achieve this using Thread join, learn how it works and when we should use it.

>> Java Thread Set Name Example

In this article, we learn how to name a thread in Java. Thread class provides setName and getName Methods to name a Thread.

>> Java Thread interrupt Example

In this article, we will learn how to interrupt a running thread. An interrupt is an indication to a thread that it should stop what it is doing and do something else. 

>> Java Thread Priority Example

In this article, we will learn how to priorities threads in a Java application. Each thread has a priority. Priorities are represented by a number between 1 and 10.

>> Java Thread isAlive Example

In this article, we will learn java.lang.Thread class provides isAlive() method to test if this thread is alive or not. A thread is alive if it has been started and has not yet died.

>> ThreadGroup Class in Java

In this article, we will learn how to group threads. Java provides a convenient way to group multiple threads in a single object. Java thread group is implemented by java.lang.ThreadGroup class.

>> Thread Class in Java

In this article, we will learn important methods of Thread class with examples.

>> Runnable Interface in Java

In this article, we will learn how to use the Runnable interface with examples.

>> Synchronization in Multithreading Java

In this article, we will learn how to use synchronization to avoid two types of problems arise when multiple threads try to read and write shared data concurrently - 1. Thread interference errors 2. Memory consistency errors.
We know that threads share Object’s variables but what if we want to have thread-local variables created at a class level. Java provides a ThreadLocal utility class to create thread-local variables. Read more to learn about how we can create ThreadLocal variables in java program.

There is a separate Java Concurrency Tutorial for high-level concurrency features Executor framework introduced with version 5.0 of the Java platform.

Executor Framework

With an Executor framework, we only have to implement the Runnable objects and send them to the executor. The executor is responsible for their execution, instantiation, and running with necessary threads. But it goes beyond that and improves performance using a pool of threads. When you send a task to the executor, it tries to use a pooled thread for the execution of this task, to avoid continuous spawning of threads.

Another important advantage of the Executor framework is the Callable interface. It's similar to the Runnable interface, but offers two improvements, which are as follows:
1. The main method of this interface, named call(), may return a result.
2. When you send a Callable object to an executor, you get an object that implements the Future interface. You can use this object to control the status and the result of the Callable object.

Java Concurrency Tutorial

In this tutorial, we will learn following high-level concurrency features Executor framework:

Comments