Contents

Multithreading

Multithreading in Java

Multithreading is a key feature in Java that enables the concurrent execution of multiple sections of a program, maximizing CPU utilization. Each such section is referred to as a thread, which represents a lightweight process.

There are two primary ways to create threads in Java:

1. By extending the Thread class

2. By implementing the Runnable interface

Creating a Thread by Extending the Thread Class:

In this approach, a class is created that extends the java.lang.Thread class. The run() method is overridden, as this is where the code for the new thread is executed. The thread is started by invoking the start() method, which internally calls the run() method.

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) {
            // Handling any exception that occurs
            System.out.println("An exception occurred");
        }
    }
}

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

				
			

Output:

				
					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, a class implements the Runnable interface, and the run() method is provided for defining the thread’s behavior. A Thread object is then created and passed an instance of the class implementing Runnable, and the start() method is called to execute the thread.

Example:

				
					// Java code for thread creation by implementing the Runnable interface
class ThreadRunnableDemo implements Runnable {
    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) {
            // Handling any exception that occurs
            System.out.println("An exception occurred");
        }
    }
}

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

				
			

A thread in Java can exist in one of several states at any given time. A thread can only be in one of these states at any particular moment. The primary states of a thread are:

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

The diagram below illustrates the various states a thread can be in during its lifecycle

Life Cycle of a Thread:

The lifecycle of a thread can be broken down into different states, which are explained below:

  • New State: A thread is in this state when it has been created but not yet started. The thread’s code is still waiting to be executed.
  • Runnable State: When a thread is ready to run, it moves into the runnable state. In this state, it may be actively running or waiting to be allocated CPU time by the thread scheduler.
  • Blocked State: A thread enters this state when it attempts to acquire a lock that is held by another thread. The thread moves back to the runnable state once it obtains the lock.
  • Waiting State: A thread moves into the waiting state when it calls wait() or join(). It remains in this state until it is notified or the thread it is waiting for completes its execution.
  • Timed Waiting State: When a thread is waiting for a specified amount of time (e.g., using sleep() or wait() with a timeout), it enters the timed waiting state.
  • Terminated State: A thread enters this state after its execution has completed or when an error occurs that causes it to terminate prematurely.

Java Enum Constants for Thread States:

In Java, the Thread.State class provides constants representing these thread states:

1. NEW: A thread that has not yet started.

				
					public static final Thread.State NEW

				
			

2. RUNNABLE: A thread that is executing in the JVM but may still be waiting for system resources such as the CPU.

				
					public static final Thread.State RUNNABLE

				
			

3. BLOCKED: A thread that is waiting for a monitor lock to enter a synchronized block or method.

				
					public static final Thread.State BLOCKED

				
			

4. WAITING: A thread that is waiting indefinitely for another thread to perform a particular action.

				
					public static final Thread.State WAITING
				
			

5. TIMED_WAITING: A thread that is waiting for a specified period of time.

				
					public static final Thread.State TIMED_WAITING

				
			

6. TERMINATED: A thread that has finished its execution.

				
					public static final Thread.State TERMINATED

				
			

Example Demonstrating Thread States:

				
					// Java program to demonstrate thread states
class MyThread implements Runnable {
    public void run() {
        // Simulating a thread going into timed waiting state
        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);

        // thread1 is in NEW state
        System.out.println("State of thread1 after creating it - " + thread1.getState());
        thread1.start();

        // thread1 is now in RUNNABLE state
        System.out.println("State of thread1 after calling .start() method - " + thread1.getState());
    }

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

        // thread2 is in NEW state
        System.out.println("State of thread2 after creating it - " + thread2.getState());
        thread2.start();

        // thread2 is now in RUNNABLE state
        System.out.println("State of thread2 after calling .start() method - " + thread2.getState());

        // Simulating a timed waiting state
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("State of thread2 after calling .sleep() method - " + thread2.getState());

        try {
            // thread1 waits for thread2 to finish
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

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

				
			

Output:

				
					State of thread1 after creating it - NEW
State of thread1 after calling .start() method - RUNNABLE
State of thread2 after creating it - NEW
State of thread2 after calling .start() method - RUNNABLE
State of thread2 after calling .sleep() method - TIMED_WAITING
State of thread1 after calling join() on thread2 - WAITING
State of thread2 after it has completed execution - TERMINATED

				
			

Java Thread Priority in Multithreading

Java operates in a multithreaded environment, where the thread scheduler assigns CPU time to threads based on their priority. When a thread is created, it is always assigned a priority, either by the JVM or explicitly by the programmer.

Thread priority in Java is represented by a number between 1 and 10. The default priority is 5, while the minimum priority is 1, and the maximum priority is 10. Java provides three constants for priority levels:

  • public static int NORM_PRIORITY (Default value of 5)
  • public static int MIN_PRIORITY (Minimum value of 1)
  • public static int MAX_PRIORITY (Maximum value of 10)

We can use the getPriority() method to retrieve the current priority of a thread and the setPriority(int newPriority) method to assign a new priority to a thread. The setPriority() method throws an IllegalArgumentException if the provided value is outside the valid range (1 to 10).

Example: Getting and Setting Thread Priorities

				
					// Java Program to demonstrate thread priorities
class PriorityExample extends Thread {
    
    // Overriding the run method to display a message
    public void run() {
        System.out.println("Inside run method of thread: " + Thread.currentThread().getName());
    }

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

        // Displaying the default priority of threads
        System.out.println("t1 priority: " + t1.getPriority());
        System.out.println("t2 priority: " + t2.getPriority());
        System.out.println("t3 priority: " + t3.getPriority());

        // Changing the priority of the threads
        t1.setPriority(3);
        t2.setPriority(7);
        t3.setPriority(10);

        // Displaying the updated priority of threads
        System.out.println("Updated t1 priority: " + t1.getPriority());
        System.out.println("Updated t2 priority: " + t2.getPriority());
        System.out.println("Updated t3 priority: " + t3.getPriority());

        // Displaying the main thread's priority
        System.out.println("Main thread priority: " + Thread.currentThread().getPriority());

        // Setting the main thread's priority to 9
        Thread.currentThread().setPriority(9);
        System.out.println("Updated main thread priority: " + Thread.currentThread().getPriority());
    }
}

				
			

Output:

				
					t1 priority: 5
t2 priority: 5
t3 priority: 5
Updated t1 priority: 3
Updated t2 priority: 7
Updated t3 priority: 10
Main thread priority: 5
Updated main thread priority: 9

				
			

Explanation:

  • Initially, all threads (t1, t2, t3) have the default priority of 5.
  • After updating, t1 has a priority of 3, t2 has 7, and t3 has 10.
  • The main thread’s priority starts at 5, but it is later updated to 9.

The thread scheduler prioritizes threads with higher priority values. For instance, in this example, t3 would have a higher chance of being executed before t2 and t1, based on its priority. However, if two threads have the same priority, the order of execution is dependent on the scheduler’s algorithm, which could be round-robin or first-come, first-serve.

Example:

				
					// Java program demonstrating child threads inheriting parent priority
class ChildThreadExample extends Thread {
    
    // Overriding the run method
    public void run() {
        System.out.println("Inside run method of thread: " + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        // Setting main thread's priority to 6
        Thread.currentThread().setPriority(6);

        // Displaying the main thread's priority
        System.out.println("Main thread priority: " + Thread.currentThread().getPriority());

        // Creating a new thread
        ChildThreadExample t1 = new ChildThreadExample();

        // Displaying the child thread's priority (should inherit from main thread)
        System.out.println("t1 thread priority: " + t1.getPriority());
    }
}

				
			

Output:

				
					Main thread priority: 6
t1 thread priority: 6

				
			

Main thread

Java provides native support for multithreaded programming, allowing multiple parts of a program to run concurrently. Each of these independent parts is known as a “thread,” with each thread following its own execution path.

When a Java application starts, the main thread is the first thread that begins execution. It is crucial for the overall program as it often spawns child threads and usually handles shutdown activities at the end.

Properties of the Main Thread:

1. It spawns other threads, often referred to as child threads.
2. It usually is the last thread to complete execution, as it might perform tasks like closing resources.

To manage the main thread, you need to get a reference to it using the currentThread() method from the Thread class. By default, the main thread has a priority of 5, and its child threads will inherit this priority.

Example: Controlling the Main Thread

				
					// Java program to demonstrate controlling the Main Thread
public class MainThreadControl extends Thread {

    public static void main(String[] args) {

        // Get reference to the main thread
        Thread mainThread = Thread.currentThread();

        // Display main thread name
        System.out.println("Current thread: " + mainThread.getName());

        // Change the name of the main thread
        mainThread.setName("PrimaryThread");
        System.out.println("After name change: " + mainThread.getName());

        // Display the main thread's priority
        System.out.println("Main thread priority: " + mainThread.getPriority());

        // Set the main thread's priority to maximum (10)
        mainThread.setPriority(Thread.MAX_PRIORITY);
        System.out.println("Main thread new priority: " + mainThread.getPriority());

        // Print a message five times from the main thread
        for (int i = 0; i < 5; i++) {
            System.out.println("Main thread iteration: " + i);
        }

        // Create a child thread within the main thread
        Thread childThread = new Thread() {
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println("Child thread iteration: " + i);
                }
            }
        };

        // Display the child thread's priority (inherited from the main thread)
        System.out.println("Child thread priority: " + childThread.getPriority());

        // Change the child thread's priority to the minimum (1)
        childThread.setPriority(Thread.MIN_PRIORITY);
        System.out.println("Child thread new priority: " + childThread.getPriority());

        // Start the child thread
        childThread.start();
    }
}

				
			

Output:

				
					Current thread: main
After name change: PrimaryThread
Main thread priority: 5
Main thread new priority: 10
Main thread iteration: 0
Main thread iteration: 1
Main thread iteration: 2
Main thread iteration: 3
Main thread iteration: 4
Child thread priority: 10
Child thread new priority: 1
Child thread iteration: 0
Child thread iteration: 1
Child thread iteration: 2
Child thread iteration: 3
Child thread iteration: 4

				
			

Explanation:

  • The main thread’s default name is main, but it’s renamed to PrimaryThread.
  • The default priority of the main thread is 5, but it’s changed to 10 (maximum).
  • The child thread inherits the main thread’s priority initially but is later reduced to 1.

Example: Deadlock Using the Main Thread

A deadlock can be created with a single thread by using the join() method on the main thread itself, causing it to wait indefinitely for itself to finish, which results in a deadlock.

				
					// Java program to demonstrate deadlock using the Main thread
public class DeadlockExample {
    public static void main(String[] args) {
        try {
            // Print statement indicating deadlock entry
            System.out.println("Entering Deadlock");

            // Deadlock: The main thread waits for itself to complete
            Thread.currentThread().join();

            // This statement will never be reached
            System.out.println("This will never be printed");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

				
			

Output:

				
					Entering Deadlock

				
			

Java.lang.Thread Class in Java

A thread is a single sequence of execution within a program. Java allows a program to have multiple threads, each of which can be managed individually. Each thread is assigned a priority, and the Java thread scheduler uses these priorities to determine the order in which threads are executed.

There are two common ways to create threads in Java:

1. By extending the Thread class.

2. By implementing the Runnable interface.

Creating a Thread by Extending the Thread Class

				
					// Method 1: Creating thread by extending Thread class

class CustomThread extends Thread {

    // The run() method defines the thread's task
    public void run() {
        System.out.println("Thread running by extending Thread class");
    }

    public static void main(String[] args) {
        // Creating an instance of CustomThread
        CustomThread thread = new CustomThread();
        
        // Starting the thread
        thread.start();
    }
}

				
			

Output:

				
					Thread running by extending Thread class

				
			

Creating a Thread by Implementing the Runnable Interface

				
					// Method 2: Creating thread by implementing Runnable interface

class MyRunnable implements Runnable {

    // Defining the task for the thread in run() method
    public void run() {
        System.out.println("Thread running by implementing Runnable interface");
    }

    public static void main(String[] args) {
        // Creating an instance of MyRunnable
        MyRunnable runnable = new MyRunnable();
        
        // Passing the Runnable object to a Thread
        Thread thread = new Thread(runnable);
        
        // Starting the thread
        thread.start();
    }
}

				
			

Output:

				
					Thread running by implementing Runnable interface

				
			
Thread Class in Java

In Java, the Thread class provides various methods to manage and control threads. Every thread starts its execution with the start() method, which looks for the run() method to execute.

Constructors of the Thread class:

ConstructorDescription
Thread()Creates a new thread.
Thread(Runnable target)Creates a new thread with a target Runnable object.
Thread(String name)Creates a new thread with a specified name.
Thread(ThreadGroup group, String name)Creates a thread within a specified group with a name.

Common Methods of the Thread Class

MethodDescription
activeCount()Returns the number of active threads in the current thread’s group.
currentThread()Returns a reference to the currently executing thread.
getName()Returns the name of the thread.
setName(String name)Sets the name of the thread.
getPriority()Returns the priority of the thread.
setPriority(int priority)Sets the priority of the thread.
isAlive()Checks if the thread is still running.
interrupt()Interrupts the thread.
join()Waits for the thread to finish.
sleep(long millis)Puts the thread to sleep for the specified time.
yield()Causes the thread to temporarily pause to allow other threads to execute.

Example:

				
					// Java program demonstrating various methods of Thread class

class Task implements Runnable {
    public void run() {
        try {
            System.out.println("Task thread sleeping for 2 seconds.");
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            System.out.println("Task thread interrupted.");
        }
    }
}

public class ThreadDemo {
    public static void main(String[] args) {
        // Creating instances of Runnable and Thread
        Task task = new Task();
        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);
        
        // Start the threads
        thread1.start();
        thread2.start();
        
        // Get thread details
        System.out.println("Thread 1 Name: " + thread1.getName());
        System.out.println("Thread 1 ID: " + thread1.getId());
        
        // Setting new name for thread
        thread1.setName("Worker-1");
        System.out.println("New name for thread 1: " + thread1.getName());

        // Check thread priority
        System.out.println("Thread 1 Priority: " + thread1.getPriority());

        // Check if thread is alive
        System.out.println("Is thread1 alive? " + thread1.isAlive());

        // Make thread1 a daemon thread
        thread1.setDaemon(true);
        System.out.println("Is thread1 a daemon thread? " + thread1.isDaemon());
        
        // Interrupt thread2
        thread2.interrupt();
        System.out.println("Is thread2 interrupted? " + thread2.isInterrupted());

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

        // Print active thread count
        System.out.println("Active thread count: " + Thread.activeCount());
    }
}

				
			

Output:

				
					Task thread sleeping for 2 seconds.
Task thread sleeping for 2 seconds.
Thread 1 Name: Thread-0
Thread 1 ID: 10
New name for thread 1: Worker-1
Thread 1 Priority: 5
Is thread1 alive? true
Is thread1 a daemon thread? true
Is thread2 interrupted? true
Active thread count: 2

				
			

Runnable interface

 Here’s a revised version of the content with new examples and the same core explanation:

The java.lang.Runnable interface is designed for classes that intend to be executed by a thread. In Java, there are two primary ways to create a new thread: by extending the Thread class or by implementing the Runnable interface. Implementing Runnable is preferred when you only need to override the run() method, and you don’t need to subclass Thread.

Steps to Create a Thread Using Runnable

1. Create a class that implements the Runnable interface and override the run() method.
2. Create a Thread object and pass the Runnable implementation to the Thread constructor.
3. Call the start() method on the Thread object, which internally invokes the run() method. This method creates a new thread that executes the code inside run(). Calling run() directly will not start a new thread; it will run in the current thread.

Example 1:

				
					public class RunnableExample {

    public static void main(String[] args) {
        System.out.println("Executing in thread: " 
                           + Thread.currentThread().getName());
        // Creating a thread with a Runnable implementation
        Thread thread1 = new Thread(new Task());
        thread1.start();
    }

    // Runnable implementation
    static class Task implements Runnable {

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() 
                               + " is executing the run() method.");
        }
    }
}

				
			

Output:

				
					Executing in thread: main
Thread-0 is executing the run() method.

				
			

In this example, two threads are active: the “main” thread, which executes the main() method, and “Thread-0”, which is created when start() is invoked. By passing the Task class (which implements Runnable) to the Thread constructor, we create a new thread that executes the run() method in Task.

Handling Exceptions in Runnable

When using the Runnable interface, you can’t throw checked exceptions from the run() method directly. However, unchecked exceptions (like RuntimeException) can be thrown. If an uncaught exception occurs, it will be handled by the thread’s exception handler, or the JVM will print a stack trace and terminate the thread.

Example 2:

				
					public class RunnableWithException {

    public static void main(String[] args) {
        System.out.println("Current thread: " 
                           + Thread.currentThread().getName());
        // Create a thread that handles exceptions inside Runnable
        Thread thread2 = new Thread(new ExceptionTask());
        thread2.start();
    }

    // Runnable implementation that handles exceptions
    static class ExceptionTask implements Runnable {

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() 
                               + " is running the task.");
            try {
                throw new IllegalArgumentException("Simulating an exception");
            } catch (IllegalArgumentException e) {
                System.out.println("Caught IllegalArgumentException!");
                e.printStackTrace();
            }

            // Simulate unchecked exception
            int result = 10 / 0;  // This will throw ArithmeticException
        }
    }
}

				
			

Output:

				
					Current thread: main
Thread-0 is running the task.
Caught IllegalArgumentException!
java.lang.IllegalArgumentException: Simulating an exception
	at RunnableWithException$ExceptionTask.run(RunnableWithException.java:19)
	at java.lang.Thread.run(Thread.java:748)
Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero
	at RunnableWithException$ExceptionTask.run(RunnableWithException.java:25)
	at java.lang.Thread.run(Thread.java:748)

				
			

Naming a thread and fetching name of current thread

A thread can be thought of as a lightweight process. Threads require fewer resources than processes and share the resources of their parent process. In Java, the main thread is the thread that starts when the program begins execution. Now, let’s explore how we can name threads in different ways.

Methods for Naming Threads

There are two primary methods for naming threads in Java:

1. Direct Method: Passing the thread name at the time of thread creation.

2. Indirect Method: Using the setName() method to set or change the name of an existing thread.

Directly Passing the Thread Name : This is the simplest way to set a thread’s name in Java. Each thread gets a default name such as Thread-0, Thread-1, and so on. However, we can explicitly provide a name for a thread when creating it, as shown in the following example:

				
					// Java Program demonstrating how to set the name
// of a thread at the time of creation

class CustomThread extends Thread {

    // Constructor that takes thread name as an argument
    CustomThread(String name) {
        super(name);  // Call parent class constructor
    }

    // Overriding the run() method
    @Override
    public void run() {
        System.out.println("Thread is running...");
    }
}

public class ThreadNamingDemo {

    public static void main(String[] args) {

        // Creating two threads with custom names
        CustomThread thread1 = new CustomThread("Worker1");
        CustomThread thread2 = new CustomThread("Worker2");

        // Printing the names of the created threads
        System.out.println("Thread 1 name: " + thread1.getName());
        System.out.println("Thread 2 name: " + thread2.getName());

        // Starting both threads
        thread1.start();
        thread2.start();
    }
}

				
			

Output:

				
					x + y = 19
x - y = 11
x * y = 60
x / y = 3
x % y = 3

				
			

Output:

				
					Thread 1 name: Worker1
Thread 2 name: Worker2
Thread is running...
Thread is running...

				
			

n this example, we created two threads and passed their names at the time of instantiation. We used the getName() method to retrieve the thread names.

Using the setName() Method : Another way to name a thread is by using the setName() method after the thread has been created. This method allows us to rename the thread at any point before or after the thread starts running.

				
					// Java Program demonstrating how to change the 
// name of a thread using the setName() method

class TaskThread extends Thread {

    @Override
    public void run() {
        System.out.println("Thread is executing...");
    }
}

public class ThreadNamingDemo {

    public static void main(String[] args) {

        // Creating two threads
        TaskThread thread1 = new TaskThread();
        TaskThread thread2 = new TaskThread();

        // Fetching default thread names
        System.out.println("Initial Thread 1 name: " + thread1.getName());
        System.out.println("Initial Thread 2 name: " + thread2.getName());

        // Starting both threads
        thread1.start();
        thread2.start();

        // Changing the names of the threads
        thread1.setName("TaskRunner1");
        thread2.setName("TaskRunner2");

        // Printing the new thread names
        System.out.println("Thread names after renaming:");
        System.out.println("Thread 1 new name: " + thread1.getName());
        System.out.println("Thread 2 new name: " + thread2.getName());
    }
}

				
			

Output:

				
					Initial Thread 1 name: Thread-0
Initial Thread 2 name: Thread-1
Thread is executing...
Thread is executing...
Thread names after renaming:
Thread 1 new name: TaskRunner1
Thread 2 new name: TaskRunner2

				
			

In this example, we first fetch and print the default thread names, then change the names using setName() and print the new names.

Fetching the Name of the Current Thread

To get the name of the currently executing thread, we can use the currentThread() method, which is part of the Thread class. This method returns a reference to the currently running thread.

				
					// Java Program demonstrating how to get the name 
// of the current executing thread

class ActiveThread extends Thread {

    @Override
    public void run() {
        System.out.println("Fetching the current thread name...");
        System.out.println(Thread.currentThread().getName());
    }
}

public class ThreadNamingDemo {

    public static void main(String[] args) {

        // Creating and starting two threads
        ActiveThread thread1 = new ActiveThread();
        ActiveThread thread2 = new ActiveThread();

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

				
			

Output:

				
					Fetching the current thread name...
Thread-0
Fetching the current thread name...
Thread-1

				
			

What does start() function do in multithreading in Java?

In Java, threads are typically created in one of two ways:

1. Extending the Thread class

2. Implementing the Runnable interface

In both approaches, we override the run() method to define the task that the thread will execute. However, instead of directly calling the run() method, we call the start() method to begin the execution of the thread. This raises an important question: why not directly call the run() method, and what makes start() essential for thread execution?

Why Call start() Instead of run()?

When we invoke the start() method, it performs the necessary setup for the thread. Specifically, it creates a new call stack for the thread and then calls the run() method on that new stack. This ensures that the thread runs independently of the main thread. On the other hand, if you directly call the run() method, it simply runs like any other method, using the same call stack as the main thread, and doesn’t create a new thread of execution.

What Happens When a Function is Called?

When a function is invoked, several operations take place:

1. The function’s arguments are evaluated.
2. A new stack frame is pushed onto the call stack.
3. Parameters are initialized with the arguments passed to the function.
4. The body of the function is executed.
5. Once the function finishes execution, the result (if any) is returned, and the current stack frame is popped from the call stack.

The role of the start() method is to create a separate call stack for the new thread, after which the run() method is automatically invoked by the JVM in the context of this new thread.

What Happens if We Call run() Directly?

Let’s consider what happens if we skip the start() method and directly call the run() method. The following example demonstrates this:

				
					// Java program to illustrate the difference between 
// calling run() and start()

class CustomThread extends Thread {
  
    @Override
    public void run() {
        try {
            // Printing the thread that is executing
            System.out.println("Thread " +
                Thread.currentThread().getId() +
                " is executing");
        } catch (Exception e) {
            // Handling any exception
            System.out.println("Exception caught");
        }
    }
}

public class ThreadDemo {

    public static void main(String[] args) {

        // Number of threads to create
        int numThreads = 5;

        // Creating multiple threads using a loop
        for (int i = 0; i < numThreads; i++) {

            CustomThread thread = new CustomThread();

            // Directly calling run() instead of start()
            thread.run();
        }
    }
}

				
			

Output:

				
					Thread 1 is executing
Thread 1 is executing
Thread 1 is executing
Thread 1 is executing
Thread 1 is executing

				
			

What does start() function do in multithreading in Java?

The Thread class in Java is used to represent a thread of execution in a program. It is part of the java.lang package and contains the sleep() method, which allows the current thread to pause its execution for a specified period. There are two overloaded versions of the sleep() method in the Thread class: one with a single parameter and another with two parameters.

The sleep() method is used to temporarily suspend the execution of the current thread. Once the specified sleep duration has passed, the thread resumes execution.

Key Points Regarding the Thread.sleep() Method:
  • Whenever the Thread.sleep() method is called, it pauses the execution of the current thread.
  • If another thread interrupts a sleeping thread, an InterruptedException is thrown.
  • Depending on the system’s load, the actual sleep time might be longer than the specified time.
  • If the system load is low, the actual sleep time will be close to the specified time.
Syntax of the Thread.sleep() Method:

There are 4 variations of the sleep() method in the Thread class:

1. public static void sleep(long millis) throws InterruptedException
2. public static void sleep(long millis) throws IllegalArgumentException
3. public static void sleep(long millis, int nanos) throws InterruptedException
4. public static void sleep(long millis, int nanos) throws IllegalArgumentException

Parameters of the sleep() Method:
  • millis: The duration in milliseconds for which the thread should pause its execution.
  • nanos: The additional time in nanoseconds (optional), ranging from 0 to 999999.
Return Type:

The sleep() method does not return a value. It has a void return type:  The single-parameter version of the sleep() method is a native method, meaning its implementation is done in a language other than Java, while the two-parameter version is implemented in Java. Both methods are static and throw a checked exception, meaning the exception must be handled using either the throws keyword or a try-catch block.

The Thread.sleep() method can be used with the main thread or any other thread created programmatically.

1. Using Thread.sleep() with the Main Thread

This example demonstrates how the Thread.sleep() method can be used to pause the execution of the main thread:

				
					// Java program to pause the main thread

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

        try {
            for (int i = 1; i <= 4; i++) {
                // Pause the main thread for 2 seconds each iteration
                Thread.sleep(2000);

                // Print the current iteration count
                System.out.println("Iteration: " + i);
            }
        } catch (InterruptedException e) {
            // Catching any interruption exception
            System.out.println("Main thread interrupted: " + e);
        }
    }
}

				
			

Output:

				
					Iteration: 1
Iteration: 2
Iteration: 3
Iteration: 4

				
			

2. Using Thread.sleep() with a Custom Thread

In this example, we create a custom thread by extending the Thread class and using the Thread.sleep() method to pause the execution of that custom thread.

				
					// Java program to pause a custom thread

class CustomThreadSleepExample extends Thread {

    @Override
    public void run() {
        try {
            for (int i = 1; i <= 3; i++) {
                // Pause the custom thread for 1 second
                Thread.sleep(1000);

                // Print the current iteration count from the custom thread
                System.out.println("Custom Thread Iteration: " + i);
            }
        } catch (InterruptedException e) {
            // Catching any interruption exception
            System.out.println("Custom thread interrupted: " + e);
        }
    }

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

				
			

Output:

				
					Custom Thread Iteration: 1
Custom Thread Iteration: 2
Custom Thread Iteration: 3

				
			

3. IllegalArgumentException When Sleep Time is Negative

This example shows what happens when an invalid (negative) value is passed to the Thread.sleep() method:

				
					// Java program to demonstrate IllegalArgumentException in Thread.sleep()

class NegativeSleepTimeExample {

    public static void main(String[] args) {

        try {
            for (int i = 0; i < 3; i++) {

                // Attempt to sleep the thread with a negative time value
                Thread.sleep(-500);

                // Print the current iteration count (won't be reached due to the exception)
                System.out.println("Iteration: " + i);
            }
        } catch (IllegalArgumentException e) {
            // Catching IllegalArgumentException for negative timeout value
            System.out.println("Caught IllegalArgumentException: Timeout value is negative");
        } catch (InterruptedException e) {
            // Catching any interruption exception
            System.out.println("Thread interrupted: " + e);
        }
    }
}

				
			

Output:

				
					Caught IllegalArgumentException: Timeout value is negative