EZ

Eduzan

Learning Hub

Back to JAVA

Multithreading in Java

Published 2025-12-12

Java

Introduction

Multithreading is a key feature in Java that enables the concurrent execution of multiple parts of a program, maximizing CPU utilization. Each independent path of execution is called a thread, which is a lightweight process.

Java provides two primary ways to create threads:

  1. By extending the Thread class
  2. By implementing the Runnable interface

Creating a Thread by Extending the Thread Class

In this approach, you create a class that extends java.lang.Thread and override the run() method. The thread starts when you call start(), which internally invokes run() on a new thread.

Example

// Java code for thread creation by extending the Thread class
class ThreadDemo extends Thread {
    public void run() {
        try {
            // Displaying the ID of the thread that is running
            System.out.println("Thread " + Thread.currentThread().getId() + " is active");
        } catch (Exception e) {
            System.out.println("An exception occurred");
        }
    }
}

// Main Class
public class ThreadExample {
    public static void main(String[] args) {
        int numberOfThreads = 5;
        for (int i = 0; i < numberOfThreads; i++) {
            ThreadDemo threadInstance = new ThreadDemo();
            threadInstance.start();
        }
    }
}

Sample Output (varies by system):

Thread 11 is active
Thread 13 is active
Thread 12 is active
Thread 15 is active
Thread 14 is active

Creating a Thread by Implementing the Runnable Interface

In this approach, you implement the Runnable interface and define the thread task inside run(). Then you pass the Runnable object to a Thread and call start().

Example

// Java code for thread creation by implementing the Runnable interface
class ThreadRunnableDemo implements Runnable {
    public void run() {
        try {
            System.out.println("Thread " + Thread.currentThread().getId() + " is active");
        } catch (Exception e) {
            System.out.println("An exception occurred");
        }
    }
}

// Main Class
public class RunnableExample {
    public static void main(String[] args) {
        int numberOfThreads = 5;
        for (int i = 0; i < numberOfThreads; i++) {
            Thread threadInstance = new Thread(new ThreadRunnableDemo());
            threadInstance.start();
        }
    }
}

Why Runnable is often preferred: your class can still extend another class (Java doesn’t allow multiple inheritance of classes).


Thread Life Cycle (Thread States)

A thread can exist in only one state at a time. The main states are:

  1. New
  2. Runnable
  3. Blocked
  4. Waiting
  5. Timed Waiting
  6. Terminated

State Explanations

  • New: Thread object is created but start() hasn’t been called.
  • Runnable: Thread is ready to run (it may actually be running or waiting for CPU).
  • Blocked: Thread is waiting to acquire a monitor lock (e.g., trying to enter a synchronized block).
  • Waiting: Thread waits indefinitely (e.g., join() or wait() without timeout).
  • Timed Waiting: Thread waits for a fixed time (e.g., sleep(ms) or wait(timeout)).
  • Terminated: Thread has finished execution.

Java Enum Constants for Thread States

Java provides these states via Thread.State:

  • NEW
  • RUNNABLE
  • BLOCKED
  • WAITING
  • TIMED_WAITING
  • TERMINATED

Example: Demonstrating Thread States

// Java program to demonstrate thread states
class MyThread implements Runnable {
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("State of thread1 after calling join() on thread2 - "
                + ThreadExample.thread1.getState());

        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class ThreadExample implements Runnable {
    public static Thread thread1;
    public static ThreadExample obj;

    public static void main(String[] args) {
        obj = new ThreadExample();
        thread1 = new Thread(obj);

        System.out.println("State of thread1 after creating it - " + thread1.getState());
        thread1.start();

        System.out.println("State of thread1 after calling .start() method - " + thread1.getState());
    }

    public void run() {
        MyThread task = new MyThread();
        Thread thread2 = new Thread(task);

        System.out.println("State of thread2 after creating it - " + thread2.getState());
        thread2.start();

        System.out.println("State of thread2 after calling .start() method - " + thread2.getState());

        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("State of thread2 after calling .sleep() method - " + thread2.getState());

        try {
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("State of thread2 after it has completed execution - " + thread2.getState());
    }
}

Java Thread Priority in Multithreading

Thread priority is an integer from 1 to 10:

  • Thread.MIN_PRIORITY = 1
  • Thread.NORM_PRIORITY = 5 (default)
  • Thread.MAX_PRIORITY = 10

You can use:

  • getPriority() to read priority
  • setPriority(int) to change priority

Note: Priority influences scheduling, but the OS/JVM scheduler may still behave differently.

Example: Getting and Setting Priorities

class PriorityExample extends Thread {
    public void run() {
        System.out.println("Inside run method of thread: " + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        PriorityExample t1 = new PriorityExample();
        PriorityExample t2 = new PriorityExample();
        PriorityExample t3 = new PriorityExample();

        System.out.println("t1 priority: " + t1.getPriority());
        System.out.println("t2 priority: " + t2.getPriority());
        System.out.println("t3 priority: " + t3.getPriority());

        t1.setPriority(3);
        t2.setPriority(7);
        t3.setPriority(10);

        System.out.println("Updated t1 priority: " + t1.getPriority());
        System.out.println("Updated t2 priority: " + t2.getPriority());
        System.out.println("Updated t3 priority: " + t3.getPriority());
    }
}

Child Thread Inherits Parent Priority

class ChildThreadExample extends Thread {
    public void run() {
        System.out.println("Running thread: " + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        Thread.currentThread().setPriority(6);
        System.out.println("Main thread priority: " + Thread.currentThread().getPriority());

        ChildThreadExample t1 = new ChildThreadExample();
        System.out.println("Child thread priority: " + t1.getPriority());
    }
}

Main Thread in Java

When the Java program starts, the first thread is the main thread.
It often:

  1. creates child threads
  2. finishes last (because it may coordinate shutdown)

Example: Controlling the Main Thread

public class MainThreadControl {
    public static void main(String[] args) {

        Thread mainThread = Thread.currentThread();

        System.out.println("Current thread: " + mainThread.getName());

        mainThread.setName("PrimaryThread");
        System.out.println("After name change: " + mainThread.getName());

        System.out.println("Main thread priority: " + mainThread.getPriority());

        mainThread.setPriority(Thread.MAX_PRIORITY);
        System.out.println("Main thread new priority: " + mainThread.getPriority());
    }
}

Deadlock Using Main Thread (Concept Example)

Calling join() on the current thread causes it to wait for itself → deadlock.

public class DeadlockExample {
    public static void main(String[] args) {
        try {
            System.out.println("Entering Deadlock");
            Thread.currentThread().join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Thread Naming in Java

1) Naming at Creation (Direct Method)

class CustomThread extends Thread {
    CustomThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println(getName() + " is running...");
    }
}

public class ThreadNamingDemo {
    public static void main(String[] args) {
        CustomThread t1 = new CustomThread("Worker1");
        CustomThread t2 = new CustomThread("Worker2");

        System.out.println("Thread 1 name: " + t1.getName());
        System.out.println("Thread 2 name: " + t2.getName());

        t1.start();
        t2.start();
    }
}

2) Naming with setName() (Indirect Method)

class TaskThread extends Thread {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " executing...");
    }
}

public class ThreadRenameDemo {
    public static void main(String[] args) {
        TaskThread thread1 = new TaskThread();
        TaskThread thread2 = new TaskThread();

        System.out.println("Initial Thread 1 name: " + thread1.getName());
        System.out.println("Initial Thread 2 name: " + thread2.getName());

        thread1.setName("TaskRunner1");
        thread2.setName("TaskRunner2");

        thread1.start();
        thread2.start();
    }
}

Fetching the Current Thread Name

public class CurrentThreadDemo {
    public static void main(String[] args) {
        System.out.println("Current thread: " + Thread.currentThread().getName());
    }
}

What Does start() Do?

Why start() instead of calling run()?

  • start() creates a new thread and a new call stack
  • JVM then calls run() on that new thread
  • Calling run() directly runs on the same thread (no new thread created)

Example: run() vs start()

class CustomThread extends Thread {
    @Override
    public void run() {
        System.out.println("Executing on thread ID: " + Thread.currentThread().getId());
    }
}

public class RunVsStartDemo {
    public static void main(String[] args) {
        CustomThread t = new CustomThread();
        t.run();   // runs in main thread
        t.start(); // runs in a new thread
    }
}

Thread.sleep() in Java

sleep() pauses the current thread temporarily.

✅ Java provides two overloads:

  1. Thread.sleep(long millis)
  2. Thread.sleep(long millis, int nanos)

It throws InterruptedException.

Example 1: Using sleep() in Main Thread

public class MainThreadSleepExample {
    public static void main(String[] args) {
        try {
            for (int i = 1; i <= 4; i++) {
                Thread.sleep(2000);
                System.out.println("Iteration: " + i);
            }
        } catch (InterruptedException e) {
            System.out.println("Main thread interrupted: " + e);
        }
    }
}

Example 2: Using sleep() in a Custom Thread

class CustomThreadSleepExample extends Thread {
    @Override
    public void run() {
        try {
            for (int i = 1; i <= 3; i++) {
                Thread.sleep(1000);
                System.out.println("Custom Thread Iteration: " + i);
            }
        } catch (InterruptedException e) {
            System.out.println("Custom thread interrupted: " + e);
        }
    }

    public static void main(String[] args) {
        new CustomThreadSleepExample().start();
    }
}

Example 3: Negative Sleep Time → IllegalArgumentException

public class NegativeSleepTimeExample {
    public static void main(String[] args) {
        try {
            Thread.sleep(-500);
        } catch (IllegalArgumentException e) {
            System.out.println("Caught IllegalArgumentException: Timeout value is negative");
        } catch (InterruptedException e) {
            System.out.println("Interrupted: " + e);
        }
    }
}