Java Unit 5 Multithreading Notes for BTech CSE (AI & ML) | BEU

Download Java Unit-5 Multithreading handwritten notes PDF for BTech CSE (AI & ML) under Bihar Engineering University (BEU). Covers processes vs threads, thread life cycle, thread states, creating threads, thread priority, synchronization, inter-thread communication, and interrupting threads with exam-oriented explanations.

Updated on

Java Unit-5 focuses on Multithreading, which is an important topic for BTech CSE (AI & ML) students of Bihar Engineering University (BEU) under the new syllabus 2025–26. This unit explains how Java programs can perform multiple tasks at the same time using threads, which improves performance and efficient CPU utilization. Questions from this unit are commonly asked in 3rd semester exams, viva, and technical interviews, making it a high-priority unit for exam preparation.

These handwritten Java multithreading notes are prepared as semester notes for BTech students, especially for CSE AI & ML, keeping BEU exam patterns in mind. The notes cover all important concepts such as difference between process and thread, thread life cycle and states, creating threads, thread priorities, thread synchronization, interrupting threads, and inter-thread communication. Each topic is explained in easy language with examples, so students can understand concepts clearly and revise quickly before exams.

This unit is ideal for students searching for Java handwritten notes, BEU Java notes, BTech semester notes, 3rd semester Java notes, and multithreading notes PDF. These notes are designed to support exam preparation, concept clarity, and interview basics, all in one place through NotesNav.

Introduction to Multithreading: Differences between multiple processes and multiple threads, thread states, creating threads, interrupting threads, thread priorities, synchronizing threads, inter thread communication.

Introduction to Multithreading

Multithreading is a feature of Java that allows a program to perform multiple tasks at the same time by executing different parts of the program concurrently.

Instead of finishing one task completely and then starting another, multithreading divides the work into smaller units so that multiple operations can run together, making better use of CPU time and improving program performance. This concept is widely used in real-world applications like web servers, games, media players, and operating systems.

  • Multithreading allows concurrent execution of tasks within a single program

  • It helps in faster execution and better CPU utilization

  • It improves responsiveness of applications

  • It is mainly useful when tasks are independent or partially dependent

  • Java provides built-in support for multithreading

Thread

A thread is the smallest unit of execution in a program that runs independently inside a process. A single program can contain multiple threads, and each thread performs a specific task.

Threads share the same memory and resources of the process, which makes communication between them fast, but also requires careful handling to avoid conflicts.

  • A thread represents a single flow of control

  • Threads run inside a process

  • Multiple threads can exist in one process

  • Threads share memory and resources

  • Thread execution is managed by the JVM

  • Threads are lightweight compared to processes

example: When you use a media player, one thread plays audio, another handles video, and another listens to user input like pause or volume control. All these tasks happen at the same time.

Multithreading

Multithreading is the ability of a program to create and run more than one thread simultaneously. Each thread performs a different task, but all threads work together within the same program. Because threads share memory, switching between threads is fast, which improves overall performance of the application.

  • Multithreading means multiple threads running concurrently

  • All threads belong to the same process

  • Threads share data and resources

  • Context switching between threads is fast

  • Multithreading increases efficiency and performance

  • Improper handling can cause data inconsistency

example: In a web browser, one thread loads a webpage, another downloads files, and another responds to user actions like scrolling or clicking.

We can achive multithreading in java by two ways:

  1. Using Thread class

  2. using Runnable class

Why Multithreading is Needed

  • To perform multiple tasks at the same time: Java programs often need to do more than one job together, like reading input, processing data, and showing output. Multithreading allows these tasks to run simultaneously instead of waiting for one to finish.

  • To improve program performance: When a Java program uses multiple threads, different parts of the program can execute in parallel, which reduces total execution time and makes the program faster.

  • To keep applications responsive: In Java GUI applications, if a long task runs in the main thread, the screen freezes. Multithreading moves long operations to separate threads so the application remains responsive.

  • To handle multiple users or requests: Java server applications receive many client requests at the same time. Multithreading allows each request to be handled by a separate thread, preventing delays.

  • To utilize CPU efficiently: Modern systems have multi-core processors. Multithreading allows Java programs to use these cores effectively by running different threads in parallel.

  • To avoid blocking during I/O operations: File access, database calls, and network operations can block execution. Using separate threads ensures other parts of the Java program continue running during such operations.

Advantages of Multithreading

  1. Improved performance: In Java, multiple threads can execute different tasks in parallel, which reduces overall execution time and makes programs run faster.

  2. Better CPU utilization: Multithreading allows Java programs to use idle CPU time efficiently by running other threads when one thread is waiting or blocked.

  3. Responsive applications: Java applications remain responsive because long-running tasks can be moved to background threads while the main thread continues handling user actions.

  4. Efficient handling of multiple tasks: Java programs can divide complex work into smaller threads, making task management simpler and more organized.

  5. Scalability in server applications: Multithreading helps Java servers handle many client requests simultaneously, improving scalability and throughput.

  6. Reduced waiting time: Threads can run independently, so one slow task does not stop the execution of other tasks in the program.

  7. Better resource sharing: Threads share the same memory space in Java, which allows fast communication and efficient use of system resources.

What is a Process

A process is a program that is currently in execution. When a Java program is started by the JVM, the operating system creates a separate process for it, which contains the program code, required memory, and system resources needed to run the program independently.

  • A process has its own separate memory space, including heap and stack, which is not shared with other processes.

  • In Java, each running application executes as a separate process, even if multiple Java programs are running on the same system.

  • A process is considered heavyweight because creating and managing it requires more time and system resources.

  • Communication between processes is slow because processes do not share memory and must use special communication mechanisms.

  • If one process crashes, it usually does not affect other processes, since each process runs independently.

Real-life example: Running a Java browser and a Java IDE at the same time creates two separate processes. If one stops working, the other continues normally.

What is a Thread

A thread is the smallest unit of execution within a process. In a Java program, a thread represents a single flow of control that performs a specific task while sharing the same memory and resources of the process in which it runs.

  • A thread runs inside a process and cannot exist independently.

  • Multiple threads can exist within a single Java process, allowing the program to perform multiple tasks at the same time.

  • Threads share the same memory space of the process, which makes communication between threads fast.

  • A thread is lightweight because creating and managing threads requires fewer system resources compared to processes.

  • In Java, thread execution is managed by the JVM, not directly by the programmer.

  • Because threads share memory, improper handling can lead to data inconsistency if synchronization is not used.

Real-life example:
In a Java-based text editor, one thread handles typing input, another performs spell checking, and another manages auto-saving, all within the same process.

Difference between Multiple Processes and Multiple Threads

Multiple Processes

Multiple Threads

A process is an independent program in execution.

A thread is a lightweight unit of execution inside a process.

Each process has its own separate memory space.

All threads of a process share the same memory space.

Process creation is slow and resource-heavy.

Thread creation is fast and lightweight.

Communication between processes is slow because memory is not shared.

Communication between threads is fast because memory is shared.

Processes are more secure due to memory isolation.

Threads are less secure because shared memory can be misused.

If one process crashes, other processes are not affected.

If one thread crashes, it may affect the entire process.

Context switching between processes takes more time.

Context switching between threads takes less time.

Processes are managed mainly by the operating system.

Threads are managed by the JVM and OS together in Java.

Suitable for running different applications.

Suitable for running multiple tasks within the same application.

Java program point of view: Java prefers multithreading inside a process because it provides better performance, faster communication, and efficient resource usage, especially for server and GUI applications.

Thread Life Cycle

The thread life cycle in Java describes the different states a thread passes through from the moment it is created until it finishes execution. A thread does not run continuously; instead, it moves between multiple states depending on CPU availability, resource locks, waiting conditions, and completion of its task.

Understanding the thread life cycle helps in writing efficient multithreaded Java programs and avoiding issues like deadlock and unnecessary waiting.

  • A thread life cycle explains how a thread is created, executed, paused, and terminated

  • Thread states are managed by the JVM and the operating system

  • A thread can move from one state to another based on program logic and system conditions

  • At any time, a thread exists in only one state.

Thread States

  1. New

  2. Runnable

  3. Running

  4. Blocked

  5. Waiting

  6. Timed Waiting

  7. Terminated

1. New State

The New state is the initial state of a thread in Java. A thread enters the New state when it is created using the Thread class, but its execution has not started yet. In this state, the thread exists only as an object in memory and has not been scheduled for execution by the JVM.

  • A thread is in New state immediately after the Thread object is created

  • The thread has not started execution. Only thread creation happens, not execution

  • The start() method is not called yet

  • The thread is not eligible for CPU time

  • The thread remains idle until start() is invoked

  • Calling run() directly does not change the state to running

  • A thread can enter New state only once in its life cycle

  • You can only call start() and stop() method when the thread in this state. if we call another method besides start() or stop() causes an IllegalThreadEexception error.

Java point of view example : When you write Thread t = new Thread();, the thread t is in the New state. It will stay in this state until t.start() is called.

2. Runnable State

The Runnable state is the state in which a thread is ready to run and is waiting for CPU time. In Java, when the start()method is called on a thread, it moves from the New state to the Runnable state. This does not mean the thread starts running immediately; it only means the thread is eligible to be executed by the CPU whenever the scheduler selects it.

  • A thread enters the Runnable state after the start() method is called

  • The thread is ready for execution but may not be running yet

  • The thread is waiting for CPU allocation by the scheduler

  • Multiple threads can be in Runnable state at the same time

  • The thread can move from Runnable to Running state when CPU is assigned

  • The thread can move back to Runnable if CPU is taken away

  • Runnable state includes both ready and running-ready conditions in Java

Java point of view example :
When t.start() is called, the thread t becomes Runnable. It will run only when the thread scheduler assigns CPU time to it.

3. Running State

The Running state is the state in which a thread is actually executing its task on the CPU. In this state, the thread’s run()method is actively running, and the thread is performing the operations defined inside it. A thread enters the Running state when the CPU scheduler selects it from the Runnable state and assigns CPU time.

  • A thread enters Running state when the CPU is allocated to it

  • The run() method starts executing in this state

  • Only one thread per CPU core can be in Running state at a time

  • The thread may not run until completion; execution depends on CPU scheduling

  • The thread can move from Running to Runnable if CPU time is taken away

  • The thread can move from Running to Blocked or Waiting state if it needs a resource

  • The thread can move to Terminated state after completing execution

Java point of view example : When the thread scheduler selects thread t and assigns CPU time, t enters the Running state and executes the code inside the run() method.

4. Blocked State

The Blocked state is the state in which a thread is temporarily stopped because it is waiting to acquire a resource or lock that is currently being used by another thread. In Java, this commonly happens when a thread tries to enter a synchronized block or method, but the required lock is already held by another thread.

  • A thread enters Blocked state when it cannot access a shared resource

  • This usually happens due to synchronization

  • The thread is waiting to acquire a lock on an object

  • The thread does not consume CPU time while blocked

  • Once the lock becomes available, the thread moves back to Runnable state

  • Blocked state is controlled by the JVM and thread scheduler

  • A blocked thread cannot proceed until the required resource is released

Java point of view example : If one thread is executing a synchronized method, and another thread tries to enter the same method, the second thread goes into the Blocked state until the first thread releases the lock.

5. Waiting State

The Waiting state is the state in which a thread pauses its execution indefinitely and waits for another thread to perform a specific action. In Java, a thread enters the Waiting state when it explicitly tells the JVM that it cannot continue until it receives a notification from another thread.

  • A thread enters Waiting state when it waits for another thread’s action

  • The waiting time is not fixed and can be infinite

  • The thread does not use CPU time while waiting

  • This state is entered using methods like wait() or join()

  • The thread remains waiting until it receives a notify() or notifyAll() call

  • After notification, the thread moves back to the Runnable state

  • Waiting state is commonly used in inter-thread communication

Java point of view example : If a thread calls wait() on an object, it enters the Waiting state and stays there until another thread calls notify() or notifyAll()on the same object.

6. Timed Waiting State

The Timed Waiting state is the state in which a thread pauses execution for a fixed period of time. Unlike the Waiting state, the thread does not wait indefinitely; it automatically becomes eligible to run again after the specified time expires.

  • A thread enters Timed Waiting state when it waits for a specific time duration

  • The waiting time is predefined and not infinite

  • The thread does not consume CPU time during this period

  • This state is entered using methods like sleep(time), wait(time), or join(time)

  • After the time expires, the thread automatically moves to the Runnable state

  • No notification from another thread is required after time completion

  • Timed Waiting is commonly used for delays, timeouts, and scheduled pauses

Java point of view example : When a thread calls Thread.sleep(1000), it enters the Timed Waiting state for 1 second and then becomes Runnable again.

7. Terminated State

The Terminated state is the state in which a thread has finished its execution and can no longer run again. Once a thread completes the execution of its run() method or stops due to an error, it enters the Terminated state permanently.

  • A thread enters Terminated state after completing the run() method

  • The thread cannot be restarted once terminated

  • Calling start() again on a terminated thread causes an exception

  • The thread releases all resources and locks it was holding

  • Terminated state marks the end of the thread life cycle

  • A thread may also terminate due to an uncaught exception

Java point of view example : When the code inside the run() method finishes executing, the thread automatically enters the Terminated state.

Thread Class in Java

The Thread class in Java is a built-in class provided in the java.lang package that is used to create and control threads. It represents a thread of execution and provides methods to start, run, pause, and manage the life cycle of a thread in a Java program.

  • The Thread class is present in the java.lang package

  • It is used to create and manage threads

  • It contains the run() method where thread code is written

  • The start() method is used to begin thread execution

  • It provides methods like sleep(), join(), and interrupt()

  • Each Thread object represents one separate thread

  • Thread scheduling is handled by the JVM

Creating a Thread using Thread Class

Creating a thread using the Thread class is the most direct way to create a thread in Java. In this approach, a new class is created by extending the built-in Thread class, and the task to be executed by the thread is written inside the run() method. When the start() method is called, the JVM creates a new thread and executes the run() method concurrently with other threads.

  • A class must extend the Thread class

  • The run() method contains the code executed by the thread

  • Calling start() creates a new thread and calls run() internally

  • Calling run() directly does not create a new thread

  • Each object of the class represents one separate thread

  • Thread scheduling is handled by the JVM, not by the programmer

  • This approach is simple but limits inheritance because Java does not support multiple inheritance

Syntax

class MyThread extends Thread {
    public void run() {
        // task to be executed by thread
    }
}

Example

class MyThread extends Thread {

    public void run() {
        System.out.println("Thread is running");
    }

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

Output

Thread is running
  • new MyThread() → thread is in New state

  • t1.start() → thread moves to Runnable state

  • JVM calls run() → thread enters Running state

  • After run() finishes → thread goes to Terminated state

Steps to Create a Thread using Thread Class

Step 1: Create a class that extends Thread

class MyThread extends Thread {
}
  • This makes MyThread a thread class

  • It inherits thread behavior from the Thread class

Step 2: Override the run() method

class MyThread extends Thread {
    public void run() {
        System.out.println("Thread task is running");
    }
}
  • The run() method contains the task

  • JVM executes this method when the thread runs

Step 3: Create an object of the thread class

MyThread t1 = new MyThread();
  • A Thread object is created

  • Thread is in New state

Step 4: Call the start() method

t1.start();
  • start() tells the JVM to create a new thread

  • Thread moves to Runnable state

  • JVM calls run() internally

Step 5: Thread executes the task

public void run() {
    System.out.println("Thread task is running");
}
  • Code inside run() executes in Running state

Step 6: Thread completes execution

  • After run() finishes, the thread enters the Terminated state

  • The thread cannot be restarted again

Complete program
class MyThread extends Thread {

    public void run() {
        System.out.println("Thread task is running");
    }

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

extend Thread → override run() → create object → call start()

How the Syntax Works

When we create a thread using the Thread class, Java uses inheritance so that our class gets all thread-related features. The actual work of multithreading is not done by us directly; it is handled by the JVM when we call specific methods in the correct order.

Step-by-step understanding of the syntax

class MyThread extends Thread {
  • This line tells Java that MyThread is a Thread

  • MyThread now gets all properties and methods of the Thread class

  • Java allows only single inheritance, so once we extend Thread, we cannot extend another class

public void run() {
    System.out.println("Thread is running");
}
  • run() contains the task that the thread will execute

  • The JVM looks for the run() method when a thread starts

  • Writing code outside run() does not run in a new thread

MyThread t1 = new MyThread();
  • This creates a Thread object

  • At this point, the thread is in the New state

  • No new thread is running yet

t1.start();
  • This is the most important line

  • start() tells the JVM to create a new thread

  • The JVM allocates memory and places the thread in Runnable state

  • The JVM then calls the run() method internally

  • start() is called only once per thread

What JVM actually does internally

  • Creates a new call stack for the thread

  • Registers the thread with the thread scheduler

  • Decides when the thread will run

  • Executes the run() method when CPU is available

Important point

t1.run();   // NOT multithreading
  • Calling run() directly does not create a new thread

  • It behaves like a normal method call

  • No new call stack is created

new Thread object → start() → Runnable → JVM calls run() → execution → termination

Example of Thread using Thread Class

1. Real-life idea: While downloading a file, a user can continue typing or using the app.
So one thread downloads the file, another keeps the app responsive.

Complete Java Program

class DownloadThread extends Thread {

    // This method contains the task of the thread
    public void run() {
        System.out.println("File downloading started...");
    }

    public static void main(String[] args) {

        // Creating thread object
        DownloadThread t1 = new DownloadThread();

        // Starting the thread
        t1.start();

        // Main thread continues its work
        System.out.println("User is using the application");
    }
}

Output

File downloading started...
User is using the application

(Order may change because threads run concurrently)

How This Code Works (Step by Step)

  • class DownloadThread extends Thread
    This makes the class capable of working as a thread.

  • run() method
    Contains the task performed by the new thread (file download).

  • DownloadThread t1 = new DownloadThread();
    A thread object is created and is in the New state.

  • t1.start();
    JVM creates a new thread, moves it to Runnable state, and calls run() internally.

  • System.out.println("User is using the application");
    This line runs in the main thread, showing both tasks run at the same time.

Key Points to Remember (Exam Focus)

  1. start() creates a new thread

  2. run() contains the thread task

  3. Both main thread and child thread run concurrently

  4. Output order is not fixed

2. Counting using thread: While one task is counting numbers in the background, the main program is also counting.
This shows both threads working simultaneously.

Java Program (Counting Example with Thread)

class CountingThread extends Thread {

    // Task performed by child thread
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println("Child Thread Count: " + i);
        }
    }

    public static void main(String[] args) {

        // Create thread object
        CountingThread t1 = new CountingThread();

        // Start child thread
        t1.start();

        // Main thread task
        for (int i = 1; i <= 5; i++) {
            System.out.println("Main Thread Count: " + i);
        }
    }
}

Output (Order may vary)

Child Thread Count: 1
Main Thread Count: 1
Child Thread Count: 2
Main Thread Count: 2
Main Thread Count: 3
Child Thread Count: 3
Main Thread Count: 4
Child Thread Count: 4
Main Thread Count: 5
Child Thread Count: 5

(Exact order can change because threads run concurrently)

How This Program Works

  • CountingThread extends Thread
    Makes the class a thread.

  • run() method
    Contains the child thread task (counting 1 to 5).

  • t1.start()
    JVM creates a new thread and calls run() internally.

  • main() loop
    Runs in the main thread and prints its own count.

  • Both loops execute at the same time, showing true multithreading.

Important Exam Points

  1. Two threads are running: main thread and child thread

  2. Both execute independently

  3. Output order is not guaranteed

  4. This proves concurrent execution

Runnable Interface

The Runnable interface in Java is used to define a task that can be executed by a thread. It does not create a thread by itself; instead, it provides the code (work) that a thread will run when the thread starts.

  • Runnable is present in the java.lang package

  • Runnable interface is better than Thread class because it supports better object-oriented design and allows multiple inheritance while still achieving multithreading.

  • It contains only one method: run()

  • It represents what to do, not how to run

  • It does not start a thread by itself

What Runnable Actually Does

Think of Runnable as a job description.

  1. Runnable tells what work should be done

  2. Thread tells when and how to run that work

Runnable = task
Thread = worker

Why Runnable is Needed

  1. Java does not support multiple inheritance

  2. If you extend Thread, you cannot extend any other class

  3. Runnable allows your class to extend another class and still use threading

  4. Runnable separates logic (task) from execution (thread)

How Runnable Works with Thread (Step by Step)

class MyTask implements Runnable {
    public void run() {
        System.out.println("Task is running");
    }
}
  • This class only defines the task

  • No thread is created yet

MyTask task = new MyTask();
  • Task object is created

  • Still no new thread

Thread t1 = new Thread(task);
  • Thread object is created

  • Task is attached to thread

t1.start();
  • JVM creates a new thread

  • JVM internally calls task.run()

Very Important Line

Thread t1 = new Thread(task);

This means:

“Create a thread and tell it which task to run

👉 Runnable tells what to run, Thread tells how to run

Common Mistake (Exam Point)

task.run();   // NOT multithreading
  • This is just a normal method call

  • No new thread is created

Only this creates a new thread:

t1.start();

Creating a Thread using Runnable Interface

Creating a thread using the Runnable interface is a more flexible and recommended way in Java. In this approach, the task of the thread is separated from the thread itself. The class implements Runnable to define the work, and a Thread object is used to run that work. This design follows better object-oriented principles and avoids the limitation of single inheritance.

Why Runnable Interface is Needed

  • Java does not support multiple inheritance, so extending Thread blocks extending another class

  • Runnable allows a class to extend another class and still use threads

  • It separates task (logic) from thread control

  • It is more reusable and more flexible

  • Preferred in real Java applications and interviews

Steps to Create a Thread using Runnable Interface

Step 1: Create a class that implements Runnable

class MyTask implements Runnable {
}
  • This class represents the task, not the thread itself

Step 2: Override the run() method

class MyTask implements Runnable {
    public void run() {
        System.out.println("Thread task is running");
    }
}
  • run() contains the code executed by the thread

Step 3: Create an object of the Runnable class

MyTask task = new MyTask();
  • This object holds the task logic

Step 4: Create a Thread object and pass Runnable object

Thread t1 = new Thread(task);
  • Thread object is created using the Runnable task

Step 5: Call start() method

t1.start();
  • JVM creates a new thread

  • JVM internally calls run()

Example (Runnable Interface)

class CountingTask implements Runnable {

    // Task performed by thread
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println("Child Thread Count: " + i);
        }
    }

    public static void main(String[] args) {

        // Create Runnable object
        CountingTask task = new CountingTask();

        // Create Thread object
        Thread t1 = new Thread(task);

        // Start thread
        t1.start();

        // Main thread task
        for (int i = 1; i <= 5; i++) {
            System.out.println("Main Thread Count: " + i);
        }
    }
}

Output (Order may vary)

Main Thread Count: 1
Child Thread Count: 1
Main Thread Count: 2
Child Thread Count: 2
Child Thread Count: 3
Main Thread Count: 3
Main Thread Count: 4
Child Thread Count: 4
Main Thread Count: 5
Child Thread Count: 5

How JVM Handles Runnable

  1. Runnable object → contains task only

  2. Thread object → handles thread creation

  3. start() → JVM creates new thread

  4. JVM calls run() of Runnable internally

Runnable = task, Thread = execution

Difference between Thread Class and Runnable Interface

Thread Class

Runnable Interface

Thread is a class in Java.

Runnable is an interface in Java.

A thread is created by extending the Thread class.

A thread is created by implementing the Runnable interface.

The class cannot extend any other class because Java does not support multiple inheritance.

The class can extend another class, so it supports better design.

The thread logic and thread control are combined in one class.

The task logic and thread control are separated.

Each object of the class represents a thread.

The Runnable object represents only the task, not the thread.

Less flexible and not recommended for large applications.

More flexible and recommended for real applications.

Uses more memory because each thread is an object.

Uses less memory because multiple threads can share the same task.

Suitable for simple programs and learning purpose.

Suitable for professional and scalable applications.

Thread class already has methods like start() and run().

Runnable has only one method: run().

Syntax to create thread:
class MyThread extends Thread { public void run() { } }

Syntax to create task:
class MyTask implements Runnable { public void run() { } }

Object creation:
MyThread t = new MyThread();

Object creation:
MyTask task = new MyTask();

Starting thread:
t.start();

Starting thread:
Thread t = new Thread(task); t.start();

Thread → extends → object → start

Runnable → implements → task → Thread object → start

Which Approach is Better: Thread or Runnable?

The Runnable interface approach is better than extending the Thread class. This is because Runnable separates the task (what to do) from the thread (how to run), supports better object-oriented design, and allows a class to extend another class while still using multithreading. Due to these reasons, Runnable is preferred in real Java applications and professional development.

Why Runnable is Better

  1. Java does not support multiple inheritance, so extending Thread blocks extending another class

  2. Runnable allows a class to extend another class and still use threads

  3. Runnable separates task logic from thread execution

  4. Better code reusability

  5. Better memory usage

  6. Preferred in large and real-world Java applications

Interrupting a Thread

Interrupting a thread means sending a signal to a running or waiting thread that it should stop its current activity and terminate gracefully.

In Java, interruption does not forcibly stop a thread. Instead, it is a polite request made by one thread to another, and it is up to the interrupted thread to handle this request properly.

Note:

  1. If any thread is in sleeping or blocked state then we can easily interrupt the execution of thread by throwingIntterruptedException.

  2. If thread not in the sleeping or wating state then thread execute normally.

  • Thread interruption is used to control thread execution

  • Java provides the interrupt() method to interrupt a thread

  • Interrupting a thread does not immediately stop it

  • The thread must check and handle the interruption

  • Mostly used with sleep(), wait(), and join()

interrupt() Method

  1. interrupt() is a method of the Thread class

  2. It sets the interrupted status of a thread

  3. If a thread is in sleeping or waiting state, it throws an exception

  4. If a thread is running normally, interruption acts as a flag

Syntax

threadName.interrupt();

How interrupt() Works Internally

  1. If the thread is running normally
    → interrupt flag is set to true

  2. If the thread is in sleep(), wait(), or join()
    → JVM throws InterruptedException

  3. The thread must handle the interruption explicitly

Key Concepts of Thread Interruption

1️⃣ void interrupt()

This method is used when one thread wants to tell another thread to stop or change its work.
When interrupt() is called, Java does not stop the thread immediately. It only sets a signal (flag) for that thread saying “you have been interrupted”.

👉 Think like this:
One thread is requesting, not forcing, the other thread to stop.

2️⃣ static boolean interrupted()

This method checks whether the current running thread is interrupted or not.
If the thread was interrupted, this method returns true.

⚠️ Important thing to remember:
After checking, this method clears the interrupt flag automatically (sets it back to false).

👉 Simple meaning:

  • It checks the interrupt

  • Then it resets the signal

3️⃣ boolean isInterrupted()

This method checks whether a particular thread is interrupted or not.
It does NOT clear the interrupt flag.

👉 Difference from interrupted():

  • isInterrupted() → only checks

  • interrupted() → checks and clears

So this method is used when you want to check the interrupt status without changing it.

4️⃣ InterruptedException

InterruptedException is a checked exception.
It occurs when a thread is sleeping, waiting, or joining, and another thread interrupts it.

This exception is thrown by methods like:

  1. Thread.sleep()

  2. wait()

  3. join()

⚠️ Important JVM behavior:
When InterruptedException is thrown, Java automatically clears the interrupt flag.

👉 Simple meaning:

  1. Thread was paused

  2. Someone interrupted it

  3. Java throws InterruptedException

  4. Interrupt signal is reset

Remember This

  1. interrupt() → sends interrupt request

  2. interrupted() → checks current thread and clears flag

  3. isInterrupted() → checks thread but does not clear flag

  4. InterruptedException → happens when sleeping/waiting thread is interrupted

We will understand this using two cases, because interrupt works differently depending on thread state.

CASE 1: Interrupting a sleeping or waiting thread

class MyThread extends Thread {
    public void run() {
        try {
            System.out.println("Thread is sleeping");
            Thread.sleep(5000); // Timed Waiting
        } catch (InterruptedException e) {
            System.out.println("Thread interrupted while sleeping");
        }
    }
}

public class InterruptDemo {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.start();

        t.interrupt(); // interrupt request
    }
}
Step 1: Object creation
MyThread t = new MyThread();
  • A thread object is created.

  • Thread state = NEW

  • No execution has started yet.

Step 2: Thread starts
t.start();
  • JVM creates a new call stack for this thread.

  • Thread state changes: NEW → RUNNABLE

  • JVM calls the run() method internally.

Step 3: Entering run()
System.out.println("Thread is sleeping");
  • Output is printed.

  • Thread is still RUNNING.

Step 4: Thread goes to sleep
Thread.sleep(5000);
  • Thread state becomes TIMED_WAITING.

  • JVM pauses this thread for 5 seconds.

  • Thread is inactive but not dead.

Step 5: Interrupt request from main thread
t.interrupt();
  • Main thread sends an interrupt signal to t.

  • JVM checks current state of thread t.

👉 Key JVM rule
If a thread is in:

  • sleep()

  • wait()

  • join()

then JVM immediately throws InterruptedException.

Step 6: InterruptedException thrown
  • JVM wakes up the sleeping thread immediately.

  • sleep() does not complete normally.

  • Control jumps to the catch block.

Step 7: Catch block executes
System.out.println("Thread interrupted while sleeping");
  • Message printed.

  • Thread execution finishes.

  • Thread state becomes TERMINATED.

Final Result

✔ Thread stopped gracefully
✔ JVM handled interruption automatically

CASE 2: Interrupting a RUNNING thread

class MyThread extends Thread {
    public void run() {
        for (int i = 1; i <= 5; i++) {
            if (Thread.currentThread().isInterrupted()) {
                System.out.println("Thread interrupted manually");
                return;
            }
            System.out.println("Running " + i);
        }
    }
}

public class InterruptCheckDemo {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.start();

        t.interrupt();
    }
}
Step 1: Thread starts
  • Thread state: NEW → RUNNABLE

  • JVM starts executing run().

Step 2: Loop begins
for (int i = 1; i <= 5; i++)
  • Thread is RUNNING.

  • No sleep / wait / join used.

Step 3: Interrupt request
t.interrupt();
  • JVM sets the interrupt flag = true.

  • Thread is NOT stopped automatically.

👉 Important JVM rule
If thread is RUNNING:

  • interrupt() only sets a flag

  • No exception is thrown

Step 4: Manual interrupt check
Thread.currentThread().isInterrupted()
  • Thread checks its own interrupt flag.

  • Flag is true.

Step 5: Graceful exit
return;
  • Thread stops execution by its own decision.

  • Thread state becomes TERMINATED.

Core JVM Concept

Thread State

What interrupt() does

sleep / wait / join

Throws InterruptedException

running normally

Sets interrupt flag only

stopped

No effect

The interrupt() method does not stop a thread forcefully; it only requests the thread to stop by setting an interrupt flag or throwing InterruptedException if the thread is blocked.

Example: Interrupting a Sleeping Thread

class MyThread extends Thread {

    public void run() {
        try {
            for (int i = 1; i <= 5; i++) {
                System.out.println("Child Thread Count: " + i);
                Thread.sleep(1000); // thread goes to timed waiting
            }
        } catch (InterruptedException e) {
            System.out.println("Thread interrupted");
        }
    }

    public static void main(String[] args) {

        MyThread t = new MyThread();
        t.start();

        // Interrupt the thread after starting
        t.interrupt();
    }
}

Output

Child Thread Count: 1
Thread interrupted

How This Code Works

  1. t.start()
    A new thread is created and starts executing run().

  2. Thread.sleep(1000)
    The thread enters Timed Waiting state.

  3. t.interrupt()
    Main thread sends an interrupt signal to the child thread.

  4. InterruptedException
    JVM throws this exception because the thread was sleeping.

  5. catch block
    The interruption is handled safely.

Important Exam Points

  1. interrupt() does not stop a thread forcefully

  2. It is a request, not a command

  3. Used mainly to stop sleeping or waiting threads

  4. Interrupted thread must handle InterruptedException

Ways to Handle Thread Interruption

The Thread class provides three methods for interrupting a thread -

  1. void interrupt() − Interrupts the thread.

  2. static boolean interrupted() − Tests whether the current thread has been interrupted.

  3. boolean isInterrupted() − Tests whether the thread has been interrupted.

1️⃣ Using InterruptedException

This happens when a thread is sleeping or waiting.

Example Program
class MyThread extends Thread {

    public void run() {
        try {
            for (int i = 1; i <= 5; i++) {
                System.out.println("Child Thread Count: " + i);
                Thread.sleep(1000); // Timed Waiting
            }
        } catch (InterruptedException e) {
            System.out.println("Thread interrupted");
        }
    }

    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.start();

        t.interrupt(); // interrupt request
    }
}
Output
Child Thread Count: 1
Thread interrupted
Working Explanation
  • start() → thread starts execution

  • sleep() → thread enters Timed Waiting

  • interrupt() → JVM throws InterruptedException

  • catch block handles interruption safely

2️⃣ Using isInterrupted() Method

Used when thread is running normally, not sleeping.

Syntax

threadObject.isInterrupted();
  • Returns true if thread is interrupted

  • Does not clear interrupt status

Example

class MyThread extends Thread {

    public void run() {
        for (int i = 1; i <= 5; i++) {
            if (isInterrupted()) {
                System.out.println("Thread interrupted, stopping work");
                return;
            }
            System.out.println("Working: " + i);
        }
    }

    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.start();
        t.interrupt();
    }
}

3️⃣ Using interrupted() Method (Static)

Syntax

Thread.interrupted();
  • Checks interruption status of current thread

  • Clears the interrupt flag after checking

Important Methods Related to Interruption

  • interrupt() → sends interruption request

  • isInterrupted() → checks interruption status

  • interrupted() → checks and clears status

What interrupt() Does NOT Do

  1. Does not stop thread forcefully

  2. Does not kill the thread

  3. Does not guarantee immediate termination

Why Thread Interruption is Needed

  1. To stop a thread safely without forcing termination

  2. To control long-running background tasks

  3. To wake up threads that are in sleeping or waiting state

  4. To handle thread cancellation properly

  5. To avoid unsafe methods like stop() (deprecated)

Thread Priority

Thread priority in Java is a mechanism used to indicate the importance of a thread to the JVM scheduler so that it can decide which thread should get CPU time first when multiple threads are in the Runnable state.

Thread priority does not guarantee execution order, but it influences the scheduling decision made by the JVM.

  • Every thread in Java has a priority value

  • A higher-priority thread is more likely to be executed before a lower-priority one, the actual execution order is not guaranteed

  • Priority is used by the JVM scheduler as a suggestion

  • Higher priority threads are generally preferred for execution

  • Thread priority does not guarantee which thread will run first

  • Actual execution depends on JVM and operating system

  • Priority is mainly used for performance tuning, not strict control

Priority Range in Java

  1. Minimum priority value is 1

  2. Maximum priority value is 10

  3. Default priority value is 5

Priority Constants in Thread Class

Java defines priority values as public static final constants inside the Thread class so that they can be used directly without creating an object and cannot be changed.

  1. public static final int MIN_PRIORITY = 1
    Represents the lowest priority a thread can have.

  2. public static final int NORM_PRIORITY = 5
    Represents the default priority assigned to a thread by the JVM.

  3. public static final int MAX_PRIORITY = 10
    Represents the highest priority a thread can have.

Meaning of public static final

  • public → accessible from anywhere

  • static → belongs to the class, not to an object

  • final → value cannot be changed

Predefined Priority Constants

  1. Thread.MIN_PRIORITY → value 1

  2. Thread.NORM_PRIORITY → value 5 (default)

  3. Thread.MAX_PRIORITY → value 10

Default Thread Priority
  1. Every thread has a default priority of 5

  2. If not set explicitly, JVM assigns NORM_PRIORITY

Methods Used in Thread Priority

to set these priority java thread class has provided two prdefined methods.

1. setPriority()

2. getPriority()

1. setPriority() Method

Used to set the priority of a thread. The value must be between 1 and 10, or an IllegalArgumentException will be thrown. It should ideally be set before calling the start() method.

Syntax:
threadObject.setPriority(int priority);

2. getPriority() Method

Used to get the current priority of a thread.

Syntax:
threadObject.gthreadObject.getPriority();
etPriority();

How Thread Priority Works Internally

  1. JVM scheduler checks Runnable threads

  2. Threads with higher priority are usually selected first

  3. Actual execution depends on:

    • JVM implementation

    • Operating system

    • CPU availability

  4. Priority does not guarantee order

Example Program Showing Thread Priority

class PriorityExample extends Thread {

    // Task executed by thread
    public void run() {
        System.out.println(
            "Thread Name: " + getName() +
            ", Priority: " + getPriority()
        );
    }

    public static void main(String[] args) {

        // Creating thread objects
        PriorityExample t1 = new PriorityExample();
        PriorityExample t2 = new PriorityExample();

        // Setting priorities
        t1.setPriority(Thread.MIN_PRIORITY); // priority 1
        t2.setPriority(Thread.MAX_PRIORITY); // priority 10
class PriorityExample extends Thread {

    // Task executed by thread
    public void run() {
        System.out.println(
            "Thread Name: " + getName() +
            ", Priority: " + getPriority()
        );
    }

    public static void main(String[] args) {

        // Creating thread objects
        PriorityExample t1 = new PriorityExample();
        PriorityExample t2 = new PriorityExample();

        // Setting priorities
        t1.setPriority(Thread.MIN_PRIORITY); // priority 1
        t2.setPriority(Thread.MAX_PRIORITY); // priority 10

        // Starting threads
        t1.start();
        t2.start();
    }
}

        // Starting threads
        t1.start();
        t2.start();
    }
}

Output

Thread Name: Thread-1, Priority: 10
Thread Name: Thread-0, PriThread Name: Thread-1, Priority: 10
Thread Name: Thread-0, Priority: 1ority: 1

Explanation

  • Two thread objects t1 and t2 are created

  • t1 is given minimum priority

  • t2 is given maximum priority

  • Both threads enter the Runnable state

  • JVM scheduler usually selects the higher priority thread first

  • Output order may change depending on system behavior

Synchronizing Threads

Synchronization in Java is a mechanism used to control the execution of multiple threads when they access shared resources. It ensures that only one thread can access the critical section of code at a time, which helps prevent data inconsistency, race conditions and incorrect results in multithreaded programs.

Synchronization is a technique through which we can control multiple threads or among the no. of threads only one thread willl enter inside the Synchronized area.

In simple words, synchronization prevents multiple threads from interfering with each other while working on the same object or data.

The main purose of the synchronization is to overcome the problem of multithreading when multiple threads are trying to access same resources at the same time on that situation it may provides some wrong result. This problem is called a race condition.

👉 Synchronization solves this problem by allowing one thread at a time

Important points

  • Synchronization is used when multiple threads share the same object.

  • It ensures thread safety by allowing only one thread at a time.

  • Java uses a lock (monitor) to achieve synchronization.

  • Every object in Java has one intrinsic lock.

  • A thread must acquire the lock before entering synchronized code.

  • Other threads must wait until the lock is released.

  • Synchronization prevents race condition and data corruption.

  • Synchronization may slightly reduce performance due to locking overhead.

Why synchronization is needed

When multiple threads access shared data without synchronization, they may read and write values at the same time. This leads to incorrect results and inconsistent data. Synchronization ensures that shared data is accessed in a controlled manner.

What Happens Without Synchronization

  1. Two or more threads access shared data simultaneously

  2. Thread execution order becomes uncontrolled

  3. Shared data may get corrupted

  4. Final output becomes incorrect

  5. This situation is called a race condition

Types of Synchronization in Java

  1. Synchronized Method

  2. Synchronized Block

synchronized Keyword - The synchronized keyword in Java is used to lock an object so that only one thread can execute the synchronized code at a time. Other threads must wait until the lock is released.

Simple Example Problem (Without Synchronization)

Imagine a bank account:

  • Balance = 1000

  • Two threads withdraw money at the same time

Without synchronization:

  • Both threads read balance as 1000

  • Both withdraw money

  • Final balance becomes incorrect

1. Synchronized Method

When a method is declared as synchronized, the entire method becomes a critical section and only one thread can execute it at a time.

The entire method is locked for one thread at a time.

Syntax
synchronized void methodName() {
    // critical section
}

Synchronization in Java – Bank Account Example

Think of a bank account with a balance of ₹1000.
Two people (threads) are trying to withdraw ₹700 at the same time.

If both threads access the account together without synchronization, the balance becomes wrong.

Case 1: WITHOUT Synchronization program

// BankAccount class represents a shared resource
class BankAccount {

    int balance = 1000;   // shared data

    // withdraw method is NOT synchronized
    void withdraw(int amount) {

        // both threads can enter here at the same time
        if (balance >= amount) {

            // deducting amount
            balance = balance - amount;

            // printing remaining balance
            System.out.println(
                Thread.currentThread().getName() +
                " withdrawn, remaining balance = " + balance
            );
        } else {

            // if balance is insufficient
            System.out.println(
                Thread.currentThread().getName() +
                " insufficient balance"
            );
        }
    }
}

// Customer thread
class Customer extends Thread {

    BankAccount account;   // reference to shared account

    Customer(BankAccount account) {
        this.account = account;
    }

    // thread execution starts here
    public void run() {
        account.withdraw(700);   // withdrawing money
    }
}

// Main class
public class WithoutSynchronization {
    public static void main(String[] args) {

        // single BankAccount object (shared)
        BankAccount account = new BankAccount();

        // two threads using same account
        Customer c1 = new Customer(account);
        Customer c2 = new Customer(account);

        c1.setName("Person-1");
        c2.setName("Person-2");

        // starting both threads
        c1.start();
        c2.start();
    }
}
Possible Output (WRONG / INCONSISTENT)
Person-1 withdrawn, remaining balance = 300
Person-2 withdrawn, remaining balance = -400
Why this output is WRONG
  1. Both threads access the same balance at the same time

  2. Both see balance = 1000

  3. Both withdraw 700

  4. Balance becomes negative, which is impossible in real life

This problem is called Race Condition.

Case 2: WITH Synchronization program

// BankAccount class represents a shared resource
class BankAccount {

    int balance = 1000;   // shared data

    // synchronized method// BankAccount class represents a shared resource
class BankAccount {

    int balance = 1000;   // shared data

    // synchronized method
    synchronized void withdraw(int amount) {

        // only ONE thread can enter this method at a time
        if (balance >= amount) {

            // deducting amount safely
            balance = balance - amount;

            // printing remaining balance
            System.out.println(
                Thread.currentThread().getName() +
                " withdrawn, remaining balance = " + balance
            );
        } else {

            // second thread will come here if balance is low
            System.out.println(
                Thread.currentThread().getName() +
                " insufficient balance"
            );
        }
    }
}

// Customer thread
class Customer extends Thread {

    BankAccount account;   // shared object reference

    Customer(BankAccount account) {
        this.account = account;
    }

    public void run() {
        account.withdraw(700);   // withdrawing money
    }
}

// Main class
public class WithSynchronization {
    public static void main(String[] args) {

        // single shared BankAccount object
        BankAccount account = new BankAccount();

        // two threads sharing same object
        Customer c1 = new Customer(account);
        Customer c2 = new Customer(account);

        c1.setName("Person-1");
        c2.setName("Person-2");

        // starting threads
        c1.start();
        c2.start();
    }
}

    synchronized void withdraw(int amount) {

        // only ONE thread can enter this method at a time
        if (balance >= amount) {

            // deducting amount safely
            balance = balance - amount;

            // printing remaining balance
            System.out.println(
                Thread.currentThread().getName() +
                " withdrawn, remaining balance = " + balance
            );
        } else {

            // second thread will come here if balance is low
            System.out.println(
                Thread.currentThread().getName() +
                " insufficient balance"
            );
        }
    }
}

// Customer thread
class Customer extends Thread {

    BankAccount account;   // shared object reference

    Customer(BankAccount account) {
        this.account = account;
    }

    public void run() {
        account.withdraw(700);   // withdrawing money
    }
}

// Main class
public class WithSynchronization {
    public static void main(String[] args) {

        // single shared BankAccount object
        BankAccount account = new BankAccount();

        // two threads sharing same object
        Customer c1 = new Customer(account);
        Customer c2 = new Customer(account);

        c1.setName("Person-1");
        c2.setName("Person-2");

        // starting threads
        c1.start();
        c2.start();
    }
}

Output (CORRECT / CONSISTENT)

Person-1 withdrawn, remaining balance = 300
Person-2 insufficient balance
Step-by-Step Code Flow (VERY IMPORTANT)
  1. Both threads share one BankAccount object

  2. withdraw() method is synchronized

  3. First thread enters and gets object lock

  4. Second thread waits

  5. Balance updated correctly

  6. Lock released

  7. Second thread executes and sees insufficient balance

What Synchronization Did (Core Concept)
  1. Applied object-level lock

  2. Allowed only one thread at a time

  3. Prevented race condition

  4. Maintained data consistency

2. Synchronized Block

A synchronized block is used when we want to synchronize only a specific part of a method, not the whole method.
This improves performance because only the critical section is locked.

Syntax
synchronized(objectReference) {
    // critical section
}

Same Bank Example

  1. Bank balance = 1000

  2. Two customers withdraw 700

  3. Same account object is shared

Case 1: WITHOUT Synchronized Block

// Shared BankAccount class
class BankAccount {

    int balance = 1000;   // shared data

    void withdraw(int amount) {

        // this whole method is NOT synchronized
        if (balance >= amount) {

            // critical section (unsafe)
            balance = balance - amount;

            System.out.println(
                Thread.currentThread().getName() +
                " withdrawn, remaining balance = " + balance
            );
        } else {
            System.out.println(
                Thread.currentThread().getName() +
                " insufficient balance"
            );
        }
    }
}

// Thread class
class Customer extends Thread {

    BankAccount account;

    Customer(BankAccount account) {
        this.account = account;
    }

    public void run() {
        account.withdraw(700);
    }
}

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

        BankAccount account = new BankAccount();

        Customer c1 = new Customer(account);
        Customer c2 = new Customer(account);

        c1.setName("Person-1");
        c2.setName("Person-2");

        c1.start();
        c2.start();
    }
}
Possible Output (WRONG)
Person-1 withdrawn, remaining balance = 300
Person-2 withdrawn, remaining balance = -400
Why this problem happens
  • Both threads enter the method together

  • Balance check happens at same time

  • Both withdraw money

  • Balance becomes incorrect

This is race condition.

Case 2: WITH Synchronized Block

// Shared BankAccount class
class BankAccount {

    int balance = 1000;   // shared data

    void withdraw(int amount) {

        // synchronized block
        synchronized (this) {
            // only this part is synchronized
            if (balance >= amount) {

                // safe critical section
                balance = balance - amount;

                System.out.println(
                    Thread.currentThread().getName() +
                    " withdrawn, remaining balance = " + balance
                );
            } else {
                System.out.println(
                    Thread.currentThread().getName() +
                    " insufficient balance"
                );
            }
        }

        // non-critical code (not synchronized)
        // other threads can execute this part
    }
}

// Thread class
class Customer extends Thread {

    BankAccount account;

    Customer(BankAccount account) {
        this.account = account;
    }

    public void run() {
        account.withdraw(700);
    }
}

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

        BankAccount account = new BankAccount();

        Customer c1 = new Customer(account);
        Customer c2 = new Customer(account);

        c1.setName("Person-1");
        c2.setName("Person-2");

        c1.start();
        c2.start();
    }
}
Output
Person-1 withdrawn, remaining balance = 300
Person-2 insufficient balance
Step-by-Step Code Flow
  1. Both threads call withdraw()

  2. Only the code inside synchronized(this) is locked

  3. First thread acquires object lock

  4. Second thread waits

  5. Balance updated safely

  6. Lock released

  7. Second thread executes and sees insufficient balance

Why Synchronized Block is Better

  • Locks only critical code

  • Improves performance

  • More flexible than synchronized method

  • Avoids unnecessary locking

Difference

  1. Synchronized method → locks entire method

  2. Synchronized block → locks specific code only

A synchronized block allows only one thread at a time to execute a critical section of code, ensuring thread safety with better performance.

Inter-Thread Communication in Java

Inter-thread communication in Java is a mechanism that allows threads to communicate with each other and coordinate their execution. It is mainly used when one thread needs to wait for another thread to complete some work before continuing.

Java provides this mechanism using the methods wait(), notify(), and notifyAll().

Important points
  • Inter-thread communication is used for cooperation between threads.

  • It avoids busy waiting (continuous checking).

  • Methods used are wait(), notify(), and notifyAll().

  • These methods belong to the Object class, not the Thread class.

  • They must be called inside a synchronized block or synchronized method.

  • Threads communicate using a shared object.

Real-Life Example (Bank Account – Deposit & Withdraw)

  1. One thread withdraws money

  2. Another thread deposits money

  3. Withdraw thread should wait if balance is low

  4. Deposit thread should notify after adding money

Case: Withdraw waits, Deposit notifies

// Shared resource
class BankAccount {

    int balance = 1000;

    // withdraw method
    synchronized void withdraw(int amount) {

        // if balance is not enough
        if (balance < amount) {
            System.out.println("Insufficient balance, waiting for deposit");

            try {
                // current thread goes into waiting state
                wait();
            } catch (InterruptedException e) {
            }
        }

        // after being notified
        balance = balance - amount;
        System.out.println("Withdrawal successful, remaining balance = " + balance);
    }

    // deposit method
    synchronized void deposit(int amount) {

        // adding money
        balance = balance + amount;
        System.out.println("Amount deposited, balance = " + balance);

        // waking up waiting thread
        notify();
    }
}

// Withdraw thread
class WithdrawThread extends Thread {

    BankAccount account;

    WithdrawThread(BankAccount account) {
        this.account = account;
    }

    public void run() {
        account.withdraw(1500);
    }
}

// Deposit thread
class DepositThread extends Thread {

    BankAccount account;

    DepositThread(BankAccount account) {
        this.account = account;
    }

    public void run() {
        account.deposit(2000);
    }
}

// Main class
public class InterThreadDemo {
    public static void main(String[] args) {

        BankAccount account = new BankAccount();

        WithdrawThread w = new WithdrawThread(account);
        DepositThread d = new DepositThread(account);

        w.start();
        d.start();
    }
}
Output
Insufficient balance, waiting for deposit
Amount deposited, balance = 3000
Withdrawal successful, remaining balance = 1500
Step-by-Step Code Flow
  1. Withdraw thread starts first.

  2. Balance is less than withdrawal amount.

  3. Withdraw thread calls wait() and releases the lock.

  4. Withdraw thread goes into waiting state.

  5. Deposit thread enters synchronized method.

  6. Deposit thread adds money.

  7. Deposit thread calls notify().

  8. Waiting withdraw thread is woken up.

  9. Withdraw thread continues execution and withdraws money.

wait()

  • Makes the current thread pause

  • Releases the object lock

  • Thread waits until notified

notify()

  • Wakes up one waiting thread

  • Lock is given after synchronized block ends

notifyAll()

  • Wakes up all waiting threads

Important Rules (Exam Focus)

  1. wait(), notify(), notifyAll() must be called inside synchronized block

  2. They work on object lock, not thread

  3. Used to coordinate threads, not stop them

Inter-thread communication allows threads to cooperate using wait and notify methods on a shared object.

NotesNav

NotesNav

Madhepura college of engineering

Frequently Asked Questions