Skip to content

Introduction to Exception Handling in Java

🧨 What is Exception Handling in Java?

📘 Definition:

Exception handling in Java is a mechanism that allows a program to detect, handle, and recover from runtime errors or unexpected conditions without crashing.

It ensures that the normal flow of the application is maintained even when errors (called exceptions) occur.


🔶 What is an Exception?

An exception is an event that disrupts the normal flow of a program during execution.

Examples:

  • Dividing by zero
  • Accessing an array out of bounds
  • Trying to open a file that doesn’t exist
  • Invalid type casting

📌 Why Exception Handling?

  • To prevent the program from crashing unexpectedly
  • To provide a graceful way of handling runtime errors
  • To help with debugging and error reporting
  • To separate error-handling code from normal code

🚦 Java Exception Hierarchy

All exceptions are derived from the class Throwable.

                   Throwable
                  /         \
             Error         Exception
                              /    \
                    Checked   Unchecked

🔸 Error

  • Serious problems like OutOfMemoryError, StackOverflowError
  • Not meant to be caught or handled

🔸 Exception

  • Problems that can be handled (like IOException, ArithmeticException)
  • Divided into:

  • Checked Exceptions (must be handled)

  • Unchecked Exceptions (optional to handle)

✅ Syntax of Exception Handling in Java

Java provides 5 keywords for exception handling:

Keyword Description
try Code that might throw an exception
catch Block that handles the exception
finally Block that always executes (used for cleanup)
throw Used to manually throw an exception
throws Declares the exceptions a method might throw

📘 Basic Example:

public class Example {
    public static void main(String[] args) {
        try {
            int result = 10 / 0;  // This will throw ArithmeticException
        } catch (ArithmeticException e) {
            System.out.println("Cannot divide by zero!");
        } finally {
            System.out.println("Finally block always executes.");
        }
    }
}

🔍 Types of Exceptions

Type Examples Must Handle?
Checked Exception IOException, SQLException ✅ Yes
Unchecked Exception ArithmeticException, NullPointerException ❌ No (but recommended)

💡 Real-life Analogy

Imagine you're withdrawing money from an ATM:

  • Normal flow: Enter card → enter PIN → withdraw cash
  • Exception: No money in account → Exception occurs
  • Handler: Show message "Insufficient funds" (instead of crashing the ATM!)

📝 Common Exam Questions

  1. What is exception handling in Java?
  2. Differentiate between checked and unchecked exceptions.
  3. Explain the try-catch-finally block with an example.
  4. What is the difference between throw and throws?

✅ Summary

  • Exception handling = catching and managing runtime errors
  • Use try, catch, finally, throw, and throws
  • Prevents program from crashing unexpectedly
  • Helps in debugging and writing reliable code

Fundamentals of Exception Handling in Java.

Understanding these fundamentals is essential for writing safe, predictable, and professional-level Java code.


📘 What is Exception Handling?

Exception Handling is a mechanism to:

  • Detect errors during program execution (runtime errors),
  • Handle those errors in a controlled way,
  • Allow the program to recover or fail gracefully.

⚙️ Fundamental Concepts of Exception Handling

🔹 1. Exception

An exception is an abnormal condition or event that occurs during the execution of a program and disrupts the normal flow of instructions.

🔹 2. Types of Exceptions

Type Description Examples
Checked Exception Checked at compile time IOException, SQLException
Unchecked Exception Checked at runtime ArithmeticException, NullPointerException
Errors Serious problems that applications should not try to handle OutOfMemoryError, StackOverflowError

🔹 3. Java Exception Class Hierarchy

                    Throwable
                    /      \
                 Error     Exception
                             /     \
                 Checked   Unchecked
  • All exceptions are subclasses of Throwable
  • Error: JVM-related serious problems (not catchable)
  • Exception: Represents application-level problems (can be handled)

🔹 4. Keywords in Exception Handling

Keyword Purpose
try Defines a block of code to test for errors
catch Handles the exception if it occurs
finally Block that always executes, used for cleanup
throw Used to throw an exception manually
throws Declares that a method may throw an exception

🔹 5. Basic Syntax

try {
    // code that may throw exception
} catch (ExceptionType e) {
    // code to handle the exception
} finally {
    // code that always runs (optional)
}

🧪 Example:

public class Example {
    public static void main(String[] args) {
        try {
            int result = 5 / 0;
        } catch (ArithmeticException e) {
            System.out.println("Can't divide by zero!");
        } finally {
            System.out.println("This will always execute.");
        }
    }
}

🔹 6. Multiple Catch Blocks

try {
    // code
} catch (IOException e) {
    // handles IO exception
} catch (ArithmeticException e) {
    // handles arithmetic error
}

✅ Catch more specific exceptions before general ones ❌ Do NOT place Exception before more specific ones—it will cause a compile error.


🔹 7. Nested try blocks

Java allows try blocks inside try blocks.

try {
    try {
        // inner try
    } catch (...) {}
} catch (...) {}

🔹 8. Custom Exception Classes

You can create your own exceptions by extending the Exception class.

class MyException extends Exception {
    public MyException(String message) {
        super(message);
    }
}

✅ Summary

Concept Description
Exception A runtime error or abnormal condition
try-catch-finally Used to handle exceptions
throw Used to throw an exception manually
throws Declares an exception from a method
Checked Exception Must be handled or declared
Unchecked Exception Optional to handle (runtime errors)

📚 Example Questions for Exams

  1. Define exception and explain types of exceptions.
  2. Explain the working of try-catch-finally block.
  3. Differentiate between throw and throws.
  4. What is the importance of exception handling?

throw and throws keywords in Java.

🔹 1. What is throw in Java?

📘 Definition:

The **throw** keyword is used to manually throw an exception in Java.

You use throw when you want to create and throw an exception yourself.

🔧 Syntax:

throw new ExceptionType("Error Message");

🧪 Example:

public class TestThrow {
    public static void main(String[] args) {
        int age = 15;

        if (age < 18) {
            throw new ArithmeticException("Access denied - You must be 18 or older.");
        }

        System.out.println("Access granted.");
    }
}

🔍 Key Points:

  • Only one exception can be thrown at a time using throw.
  • After throw, the program stops executing unless the exception is caught.
  • The object thrown must be an instance of a subclass of Throwable.

🔹 2. What is throws in Java?

📘 Definition:

The **throws** keyword is used in a method declaration to specify that the method might throw one or more exceptions.

You use throws to inform the caller that it needs to handle or declare the exception.

🔧 Syntax:

returnType methodName() throws ExceptionType1, ExceptionType2 {
    // method body
}

🧪 Example:

import java.io.*;

public class TestThrows {
    public static void readFile() throws IOException {
        FileReader file = new FileReader("data.txt");  // Might throw IOException
        file.read();
        file.close();
    }

    public static void main(String[] args) {
        try {
            readFile();  // You must handle the exception here
        } catch (IOException e) {
            System.out.println("File not found or error reading file.");
        }
    }
}

🔍 Key Points:

  • Used for checked exceptions only (like IOException, SQLException)
  • Tells the caller of the method: “Be ready to handle this exception.”

🔁 Difference Between throw and throws

Feature throw throws
Purpose To actually throw an exception To declare possible exceptions
Placement Inside method body In method declaration
Number of exceptions Only one can be thrown at a time Multiple exceptions can be declared
Usage Followed by exception object Followed by exception class names
Example throw new IOException(); throws IOException, SQLException

📝 Common Exam Questions

  1. Differentiate between throw and throws in Java.
  2. Write a program that demonstrates the use of throw.
  3. Write a method that uses throws to declare multiple exceptions.

✅ Summary

  • ✅ Use throw to manually generate an exception.
  • ✅ Use throws to declare exceptions a method might throw.
  • Both help in robust error handling and clean exception reporting.

try and catch

  • the two core components of Java’s exception handling mechanism.

🔹 What is try in Java?

📘 Definition:

The try block contains the code that might throw an exception. You put the risky or error-prone code inside this block.

Purpose:

  • To test a block of code for exceptions.
  • If an exception occurs inside try, control immediately jumps to the appropriate catch block.

Syntax:

try {
    // code that might throw an exception
}

🔹 What is catch in Java?

📘 Definition:

The catch block handles the exception that occurs inside the try block.

Purpose:

  • To catch and handle exceptions so the program can continue or terminate gracefully.
  • It receives an exception object that provides information about the error.

Syntax:

catch (ExceptionType e) {
    // code to handle the exception
}

🔹 How do try and catch work together?

try {
    // risky code
} catch (ExceptionType e) {
    // handler code
}
  • The program tries to execute the code in try.
  • If no exception occurs, catch is skipped.
  • If an exception of type ExceptionType occurs, control moves to the matching catch block.
  • If exception is not caught, it propagates up and may crash the program.

🧪 Example:

public class TryCatchExample {
    public static void main(String[] args) {
        try {
            int division = 10 / 0;  // Causes ArithmeticException
            System.out.println(division);
        } catch (ArithmeticException e) {
            System.out.println("Error: Cannot divide by zero!");
        }
    }
}

Output:

Error: Cannot divide by zero!

🔹 Multiple Catch Blocks

You can handle different exceptions differently by using multiple catch blocks:

try {
    // code
} catch (ArithmeticException e) {
    // handle divide by zero
} catch (NullPointerException e) {
    // handle null pointer
}

🔹 Important Notes

  • You must catch checked exceptions or declare them with throws.
  • The catch block only catches exceptions that match its parameter type or subclasses.
  • try must be followed by at least one catch block or a finally block.

📝 Common Exam Questions

  1. What is the purpose of the try block in exception handling?
  2. How does the catch block work? Give an example.
  3. Can there be multiple catch blocks? Explain with an example.

✅ Summary

Keyword Purpose
try Contains code that may throw exceptions
catch Handles the thrown exceptions

Threads in Java

  • a fundamental concept for writing programs that can do multiple things at the same time (concurrency).

🔹 What is a Thread?

📘 Definition:

A thread is the smallest unit of execution within a process. It is like a lightweight subprocess.

  • A process can have multiple threads running concurrently.
  • Threads share the same memory space but execute independently.

Real-life analogy:

Think of a process as a restaurant kitchen, and threads are the chefs who work simultaneously on different tasks (cutting veggies, cooking, plating).


🔹 Why Use Threads?

  • To perform multiple tasks simultaneously (parallelism).
  • To improve the performance of applications.
  • Useful in GUI apps (so UI doesn’t freeze), servers (handle multiple clients), games, etc.

🔹 Thread Lifecycle in Java

A thread can be in one of these states:

State Description
New Thread object created but not started yet.
Runnable Ready to run and waiting for CPU time.
Running Thread is executing.
Blocked/Waiting Waiting for a resource or signal.
Terminated Thread has finished execution or stopped.

🔹 How to Create Threads in Java?

1. By Extending Thread Class

class MyThread extends Thread {
    public void run() {
        System.out.println("Thread running");
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start();  // Starts the new thread and calls run()
    }
}

2. By Implementing Runnable Interface

class MyRunnable implements Runnable {
    public void run() {
        System.out.println("Runnable running");
    }
}

public class Main {
    public static void main(String[] args) {
        Thread t1 = new Thread(new MyRunnable());
        t1.start();
    }
}

🔹 Difference between start() and run()

Method Description
start() Creates a new thread and calls run() internally
run() Just executes the code on current thread like a normal method call

🔹 Thread Methods

  • start() – start thread execution
  • run() – thread's entry point
  • sleep(milliseconds) – pauses thread for a given time
  • join() – waits for thread to finish
  • yield() – temporarily pause to allow other threads to execute

🔹 Example:

class MyThread extends Thread {
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println("Thread: " + i);
            try {
                Thread.sleep(500);  // Pause for 0.5 seconds
            } catch (InterruptedException e) {
                System.out.println("Thread interrupted");
            }
        }
    }
}

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

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

🔹 Benefits of Threads

  • Efficient use of CPU
  • Better application responsiveness
  • Allows concurrent operations like file reading while UI remains responsive

📝 Common Exam Questions

  1. What is a thread in Java?
  2. Explain two ways to create a thread.
  3. What is the difference between start() and run()?
  4. Describe the lifecycle of a thread.

✅ Summary

  • A thread is a lightweight process — multiple threads run inside a process.
  • Two ways to create threads: extend Thread or implement Runnable.
  • Use start() to begin a new thread.
  • Threads help in performing multiple tasks concurrently.

Synchronization in Java

🔹 What is Synchronization?

📘 Definition:

Synchronization in Java is a mechanism to control access to shared resources by multiple threads to avoid conflicts and inconsistent data.


🔹 Why Synchronization is Needed?

When multiple threads try to access and modify shared data at the same time, it can cause problems like:

  • Race conditions: When two threads change a variable simultaneously leading to incorrect results.
  • Data inconsistency: Corrupted or unexpected data due to unsynchronized access.

🔹 How Synchronization Works?

  • Java provides the synchronized keyword.
  • When a method or block is marked as synchronized, only one thread can access it at a time.
  • Other threads trying to access the synchronized code will wait (blocked) until the lock is released.

🔹 Synchronization on Methods

public synchronized void method() {
    // critical section code (shared resource)
}
  • The thread must acquire the lock on the object before entering.
  • Once inside, no other thread can enter any synchronized method on the same object.

🔹 Synchronization on Blocks

public void method() {
    synchronized(this) {
        // critical section code
    }
}
  • Synchronizes only a part of the code, improving performance by limiting locked code.

🔹 Locks in Synchronization

  • Every object in Java has a lock (monitor).
  • Synchronization is about acquiring and releasing the object's lock.
  • Only the thread holding the lock can execute synchronized code on that object.

🧪 Example:

class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

public class TestSync {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("Count: " + counter.getCount());  // Should print 2000
    }
}

Without synchronization, the count value may be incorrect due to race conditions.


🔹 Important Points

  • Synchronization can reduce performance because threads wait to acquire locks.
  • Use synchronization only when necessary to protect shared mutable data.
  • Use volatile keyword when you want visibility but not atomicity.

📝 Common Exam Questions

  1. What is synchronization in Java and why is it necessary?
  2. How does the synchronized keyword work?
  3. What is the difference between synchronized methods and synchronized blocks?
  4. Explain race conditions and how synchronization solves it.

✅ Summary

Concept Description
Synchronization Ensures only one thread accesses critical code at a time
synchronized Keyword to lock methods or blocks on an object
Lock/Monitor Every object has a lock used for synchronization
Race condition When threads interfere causing inconsistent data

messaging

🔹 What is Messaging?

📘 Definition:

Messaging is the process of sending and receiving data or information between two or more entities (such as programs, components, or systems), usually asynchronously.

In the context of software and distributed systems, messaging allows different parts of an application or different applications to communicate and coordinate without being directly connected all the time.


🔹 Why is Messaging Important?

  • Enables communication between different software components or services.
  • Supports asynchronous processing — sender and receiver don’t need to interact at the same time.
  • Helps build loosely coupled systems, improving scalability and reliability.
  • Commonly used in distributed systems, microservices, client-server models, etc.

🔹 Types of Messaging

Type Description Example/Use Case
Synchronous Sender waits for receiver to process and respond immediately. HTTP request-response
Asynchronous Sender sends message and continues; receiver processes it later. Message queues like RabbitMQ
One-way Message sent without expecting a response. Event notifications
Two-way Sender expects a reply (request-response pattern). Remote Procedure Calls (RPC)

🔹 Messaging Models

  • Point-to-Point (Queue): Messages sent to a queue; one receiver consumes a message.
  • Publish-Subscribe (Topic): Messages published to a topic; multiple subscribers receive messages.

🔹 Messaging in Java Context

In Java, messaging often refers to Java Messaging Service (JMS):

  • JMS is a Java API that allows applications to create, send, receive, and read messages.
  • Supports asynchronous communication between distributed components.
  • Two main messaging models: Queue (Point-to-Point) and Topic (Publish-Subscribe).

🧪 Simple analogy:

Imagine a post office system:

  • You drop a letter (message) into a mailbox (queue).
  • The recipient collects the letter when available.
  • You don’t have to wait in person; this is asynchronous messaging.

📝 Common Exam Questions

  1. Define messaging in the context of distributed systems.
  2. Differentiate synchronous and asynchronous messaging.
  3. What is the point-to-point messaging model?
  4. Explain the publish-subscribe messaging model.

✅ Summary

Aspect Explanation
Messaging Sending and receiving data/information
Sync vs Async Sync: waits for reply; Async: continues immediately
Models Point-to-point (queue), publish-subscribe (topic)
Use in Java Java Messaging Service (JMS) API

Runnable interface in Java.

🔹 What is the Runnable Interface?

📘 Definition:

Runnable is a functional interface in Java that represents a task that can be executed by a thread. It only has one method, run(), which contains the code that defines the task.


🔹 Purpose of Runnable

  • It is used to define a unit of work or task that can be run in a thread.
  • Allows you to separate the task from the Thread object.
  • Provides more flexibility, especially when your class needs to extend some other class (since Java supports only single inheritance).

🔹 The Runnable Interface Method

public interface Runnable {
    public abstract void run();
}
  • You implement this interface and provide the body for the run() method.
  • The run() method is where you put the code that should execute in a separate thread.

🔹 How to Use Runnable?

Step 1: Implement Runnable in a class

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

Step 2: Create a Thread object passing the Runnable instance

public class TestRunnable {
    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);  // Pass Runnable to Thread
        thread.start();  // Start the new thread
    }
}

🔹 Why Use Runnable Instead of Extending Thread?

Runnable Interface Extending Thread Class
Allows your class to extend from another class Limits you because Java supports single inheritance
More flexible to reuse the task in multiple threads Thread object and task are combined
Cleaner separation of task and thread Less flexible

🧪 Example:

class MyTask implements Runnable {
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println("Runnable Thread: " + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                System.out.println("Thread interrupted");
            }
        }
    }
}

public class RunnableDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyTask());
        thread.start();

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

📝 Common Exam Questions

  1. What is the Runnable interface in Java?
  2. How is Runnable different from extending the Thread class?
  3. Write a program using Runnable to create a thread.

✅ Summary

  • Runnable is a functional interface with the single method run().
  • Implementing Runnable lets you define a task that a thread will execute.
  • It provides better design flexibility than extending Thread.
  • You create a thread by passing a Runnable instance to a Thread constructor.

Thread class in Java.

🔹 What is the Thread Class?

📘 Definition:

The Thread class in Java represents a thread of execution — a lightweight process that can run concurrently with other threads.

  • It’s part of java.lang package.
  • You use the Thread class to create and control threads.

🔹 How Threads Work in Java?

  • A Java program runs in at least one thread (the main thread).
  • You can create additional threads for multitasking.
  • Each thread runs independently but shares the same memory within the process.

🔹 Ways to Create a Thread Using Thread Class

1. Extend the Thread class and override its run() method.

class MyThread extends Thread {
    public void run() {
        System.out.println("Thread running");
    }
}

public class TestThread {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start();  // Starts the thread and calls run()
    }
}

2. Alternatively, you can pass a Runnable object to a Thread constructor (we covered this before).


🔹 Important Methods in Thread Class

Method Description
start() Starts the thread and calls the run() method in a new thread.
run() Contains the code executed by the thread.
sleep(long millis) Pauses the thread for specified milliseconds.
join() Waits for a thread to finish before continuing.
setPriority(int p) Sets the priority of a thread (1 to 10).
getName() Returns the thread’s name.
setName(String name) Sets the thread’s name.
interrupt() Interrupts a thread (used to stop waiting or sleeping threads).
isAlive() Checks if the thread is still running.

🔹 start() vs run() Methods

start() run()
Creates a new thread and calls run() internally Just executes run() method on the current thread (like a normal method call)
Should be called to start the thread execution Should not be called directly to create a new thread

🧪 Example:

class MyThread extends Thread {
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println("Thread: " + i);
            try {
                Thread.sleep(500);  // Pause for 0.5 seconds
            } catch (InterruptedException e) {
                System.out.println("Thread interrupted");
            }
        }
    }
}

public class TestThreadClass {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.setName("MyThread-1");
        t1.start();

        System.out.println("Main thread finished");
    }
}

🔹 Thread Life Cycle States Recap

  • New: Thread object created but not started.
  • Runnable: Ready to run but waiting for CPU.
  • Running: Thread executing.
  • Blocked/Waiting: Waiting for resource or signal.
  • Terminated: Thread finished or stopped.

📝 Common Exam Questions

  1. How do you create a thread by extending the Thread class?
  2. What is the difference between start() and run() methods?
  3. Name some important methods of the Thread class.
  4. Explain the thread life cycle briefly.

✅ Summary

Concept Description
Thread class Represents a thread of execution in Java
Create thread Extend Thread and override run(), then call start()
Key methods start(), run(), sleep(), join(), interrupt()
start() vs run() start() creates new thread; run() executes in current thread

multiple threads in Java.

🔹 Creating Multiple Threads in Java

You can create multiple threads by either:

  • Extending the Thread class multiple times, or
  • Implementing the Runnable interface multiple times.

1. Using Thread Class (Extending Thread)

class MyThread extends Thread {
    private String threadName;

    MyThread(String name) {
        threadName = name;
    }

    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println(threadName + " is running: " + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                System.out.println(threadName + " interrupted.");
            }
        }
        System.out.println(threadName + " finished.");
    }
}

public class MultiThreadExample {
    public static void main(String[] args) {
        MyThread t1 = new MyThread("Thread-1");
        MyThread t2 = new MyThread("Thread-2");
        MyThread t3 = new MyThread("Thread-3");

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

class MyRunnable implements Runnable {
    private String threadName;

    MyRunnable(String name) {
        threadName = name;
    }

    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println(threadName + " is running: " + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                System.out.println(threadName + " interrupted.");
            }
        }
        System.out.println(threadName + " finished.");
    }
}

public class MultiRunnableExample {
    public static void main(String[] args) {
        Thread t1 = new Thread(new MyRunnable("Runnable-1"));
        Thread t2 = new Thread(new MyRunnable("Runnable-2"));
        Thread t3 = new Thread(new MyRunnable("Runnable-3"));

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

🔹 What Happens When You Run This?

  • Each thread runs independently and concurrently.
  • Output will be interleaved because threads run in parallel.
  • Threads share the same CPU but switch rapidly.

🔹 Notes

  • Use Thread.sleep() to simulate work or pause.
  • You can use join() to wait for a thread to finish if needed.
  • Creating many threads may impact performance, so be cautious.

📝 Common Exam Questions

  1. How do you create multiple threads in Java?
  2. Explain the difference between extending Thread and implementing Runnable for multiple threads.
  3. What happens when you call start() on multiple thread objects?

✅ Summary

Method Description
Extend Thread Create multiple subclasses and start each
Implement Runnable Create multiple Runnable objects, pass to Thread, and start
Thread.sleep() Pause thread execution to simulate work
Threads run concurrently Outputs from threads interleave

Interthread Communication

🔹 What is Interthread Communication?

📘 Definition:

Interthread Communication refers to the process where two or more threads communicate with each other to coordinate their actions, especially when sharing resources.

This is crucial to avoid problems like:

  • Race conditions
  • Deadlocks
  • Data inconsistency

🔹 Why is Interthread Communication Needed?

  • Sometimes one thread must wait for another thread to complete or produce data.
  • Threads need to send signals or notifications to each other about changes in state.
  • It helps in synchronizing tasks that depend on each other.

🔹 How Does Java Support Interthread Communication?

Java provides three key methods inside Object class used for interthread communication:

Method Description
wait() Makes the current thread wait until another thread calls notify() or notifyAll() on the same object. The thread releases the lock and waits.
notify() Wakes up one thread waiting on the object's monitor. If multiple threads wait, one is chosen arbitrarily.
notifyAll() Wakes up all threads waiting on the object's monitor.

🔹 Important Points

  • wait(), notify(), and notifyAll() must be called within a synchronized block or method to hold the object's lock.
  • When a thread calls wait(), it releases the lock and goes to the waiting state.
  • When notified, the thread moves to the runnable state but must re-acquire the lock before continuing.

🧪 Example: Producer-Consumer Problem

class Data {
    private int number;
    private boolean available = false;

    public synchronized void produce(int num) throws InterruptedException {
        while (available) {
            wait();  // Wait if data is available (consumer has not consumed yet)
        }
        this.number = num;
        System.out.println("Produced: " + num);
        available = true;
        notify();  // Notify consumer
    }

    public synchronized void consume() throws InterruptedException {
        while (!available) {
            wait();  // Wait if data is not yet produced
        }
        System.out.println("Consumed: " + number);
        available = false;
        notify();  // Notify producer
    }
}

class Producer implements Runnable {
    Data data;

    Producer(Data data) {
        this.data = data;
    }

    public void run() {
        for (int i = 1; i <= 5; i++) {
            try {
                data.produce(i);
                Thread.sleep(500);
            } catch (InterruptedException e) { }
        }
    }
}

class Consumer implements Runnable {
    Data data;

    Consumer(Data data) {
        this.data = data;
    }

    public void run() {
        for (int i = 1; i <= 5; i++) {
            try {
                data.consume();
                Thread.sleep(500);
            } catch (InterruptedException e) { }
        }
    }
}

public class InterThreadCommExample {
    public static void main(String[] args) {
        Data data = new Data();
        Thread producer = new Thread(new Producer(data));
        Thread consumer = new Thread(new Consumer(data));

        producer.start();
        consumer.start();
    }
}

🔹 Explanation of the Example

  • The producer produces data and waits if the consumer hasn’t consumed it yet.
  • The consumer consumes data and waits if there is no data available.
  • wait() causes threads to wait and release the lock.
  • notify() wakes up waiting threads to continue.

📝 Common Exam Questions

  1. What is interthread communication?
  2. Explain the use of wait(), notify(), and notifyAll().
  3. Why must wait(), notify(), and notifyAll() be called within synchronized blocks?
  4. Describe the producer-consumer problem and how interthread communication solves it.

✅ Summary

Method Purpose
wait() Pause thread and release lock until notified
notify() Wake up one waiting thread
notifyAll() Wake up all waiting threads
Synchronization Must use synchronized block or method

Deadlock in Java multithreading.

🔹 What is Deadlock?

📘 Definition:

A deadlock is a situation in multithreading where two or more threads are blocked forever, each waiting for a resource held by the other, creating a cycle of dependency with no thread able to proceed.


🔹 How Does Deadlock Occur?

Deadlock happens when all the following conditions are true simultaneously (known as Coffman conditions):

  1. Mutual Exclusion: At least one resource is held in a non-shareable mode (only one thread can use it at a time).
  2. Hold and Wait: A thread holding at least one resource is waiting to acquire additional resources held by other threads.
  3. No Preemption: Resources cannot be forcibly taken away from threads holding them.
  4. Circular Wait: A closed chain of threads exists, where each thread holds a resource the next thread needs.

🔹 Visualizing Deadlock

Imagine:

  • Thread A holds Resource 1 and waits for Resource 2.
  • Thread B holds Resource 2 and waits for Resource 1.

Neither can proceed — they’re stuck forever, causing a deadlock.


🔹 Deadlock in Java Example

public class DeadlockDemo {
    public static void main(String[] args) {
        final Object resource1 = "Resource 1";
        final Object resource2 = "Resource 2";

        // Thread 1 tries to lock resource1 then resource2
        Thread t1 = new Thread(() -> {
            synchronized(resource1) {
                System.out.println("Thread 1 locked resource 1");

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

                synchronized(resource2) {
                    System.out.println("Thread 1 locked resource 2");
                }
            }
        });

        // Thread 2 tries to lock resource2 then resource1
        Thread t2 = new Thread(() -> {
            synchronized(resource2) {
                System.out.println("Thread 2 locked resource 2");

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

                synchronized(resource1) {
                    System.out.println("Thread 2 locked resource 1");
                }
            }
        });

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

What happens here?

  • Thread 1 locks resource1 and waits for resource2.
  • Thread 2 locks resource2 and waits for resource1.
  • Both threads wait forever → deadlock.

🔹 How to Avoid Deadlock?

1. Avoid Nested Locks

Reduce the scope of synchronized blocks and avoid locking multiple resources simultaneously.

2. Lock Ordering

Always acquire locks in the same order in all threads.

Example: Both threads should lock resource1 then resource2.

3. Use tryLock() (with ReentrantLock)

Try to acquire locks with timeout, so you can back off if unable to get the lock.

4. Avoid Hold and Wait

Ensure threads acquire all required resources at once or release held resources before waiting.

5. Deadlock Detection Tools

Use thread dumps and debugging tools to detect deadlocks during testing.


🔹 Detecting Deadlock

  • In Java, you can use jstack to get thread dumps and look for deadlock patterns.
  • Tools like VisualVM, JConsole can help visualize deadlocks.

📝 Common Exam Questions

  1. Define deadlock in Java.
  2. List the four conditions required for deadlock.
  3. Write a simple Java program demonstrating deadlock.
  4. How can deadlock be prevented?

✅ Summary

Aspect Description
Deadlock Threads waiting forever for each other’s resources
Conditions Mutual exclusion, hold & wait, no preemption, circular wait
Avoidance Lock ordering, avoid nested locks, use tryLock, avoid hold & wait
Detection Thread dumps, monitoring tools

suspending, resuming, and stopping

🔹 Suspending, Resuming, and Stopping Threads in Java

Background:

Java provides some methods in the Thread class for suspending, resuming, and stopping threads:

  • suspend()
  • resume()
  • stop()

However, these methods are deprecated and unsafe! Using them can lead to deadlocks and inconsistent thread states.


1. Thread.suspend()

  • Temporarily pauses the execution of a thread.
  • The thread stays suspended until resumed.
  • Deprecated because suspending a thread holding locks can cause other threads to deadlock waiting for those locks.

2. Thread.resume()

  • Resumes a thread that was suspended by suspend().
  • Deprecated for the same reasons.

3. Thread.stop()

  • Immediately terminates a thread.
  • Deprecated because it can stop a thread in the middle of execution, potentially leaving shared resources in an inconsistent state (like partially updated variables).

🔹 Why Are These Methods Deprecated?

  • Deadlock risks: suspend() can pause a thread while it holds a lock, blocking other threads indefinitely.
  • Inconsistent state: stop() terminates a thread abruptly, without proper cleanup.
  • Unsafe resource management: Locks or resources may never be released.

Java encourages safer ways to manage thread lifecycle using flags or interrupts:

Using a flag to pause/resume:

class ControlledThread extends Thread {
    private volatile boolean suspended = false;

    public void run() {
        while (true) {
            synchronized(this) {
                while (suspended) {
                    try {
                        wait();  // wait until notified to resume
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                }
            }
            // Perform thread work here
            System.out.println("Thread running...");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    public synchronized void suspendThread() {
        suspended = true;
    }

    public synchronized void resumeThread() {
        suspended = false;
        notify();
    }
}

Using interrupt() to stop:

class InterruptibleThread extends Thread {
    public void run() {
        try {
            while (!Thread.currentThread().isInterrupted()) {
                // Thread work here
                System.out.println("Thread running...");
                Thread.sleep(500);
            }
        } catch (InterruptedException e) {
            System.out.println("Thread interrupted and stopping.");
            Thread.currentThread().interrupt(); // preserve interrupt status
        }
    }
}

public class ThreadControlExample {
    public static void main(String[] args) throws InterruptedException {
        ControlledThread t = new ControlledThread();
        t.start();

        Thread.sleep(2000);
        t.suspendThread();
        System.out.println("Thread suspended");

        Thread.sleep(2000);
        t.resumeThread();
        System.out.println("Thread resumed");

        Thread.sleep(2000);
        t.interrupt();  // Using interrupt to stop
        System.out.println("Thread interrupted for stopping");
    }
}

📝 Key Points for Exams

Method Status What it does Why deprecated / problems
suspend() Deprecated Pauses thread execution Can cause deadlocks if thread holds locks
resume() Deprecated Resumes suspended thread Same as suspend
stop() Deprecated Stops thread immediately Unsafe, may corrupt shared data
wait()/notify() Recommended Use in custom flags for safe pause/resume Safer thread coordination
interrupt() Recommended Used to signal thread to stop or wake up Cooperative way to stop a thread safely

Summary

  • Avoid suspend(), resume(), stop() methods in Java.
  • Use flags + wait/notify for pause/resume behavior.
  • Use interrupt() method for safely stopping threads.
  • Proper synchronization and handling of interrupts is essential for safe thread control.

Multithreading

🔹 What is Multithreading?

📘 Definition:

Multithreading is a Java feature that allows a program to run multiple threads simultaneously within a single process.

  • A thread is the smallest unit of execution.
  • Multithreading enables concurrent execution of two or more parts of a program for maximum CPU utilization.

🔹 Why Use Multithreading?

  • To perform multiple tasks at the same time (e.g., loading data, user interface, background calculations).
  • To improve application performance on multi-core processors.
  • To enhance resource sharing among threads.
  • To create responsive programs where one thread can run in the background without blocking others.

🔹 Key Concepts

Term Description
Process Program in execution, with its own memory space.
Thread Lightweight sub-process, shares process memory.
Main Thread The initial thread created by JVM to run main() method.
Context Switching CPU switching between threads, enabling multitasking.

🔹 How to Create Threads in Java?

1. Extending the Thread Class

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

public class Test {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start();  // starts new thread and calls run()
    }
}

2. Implementing the Runnable Interface (Preferred)

class MyRunnable implements Runnable {
    public void run() {
        System.out.println("Thread running: " + Thread.currentThread().getName());
    }
}

public class Test {
    public static void main(String[] args) {
        Thread t1 = new Thread(new MyRunnable());
        t1.start();
    }
}

🔹 Thread Lifecycle

State Description
New Thread created but not started yet
Runnable Ready to run and waiting for CPU
Running Thread is executing
Waiting/Blocked Waiting for a resource or event
Terminated Thread finished or stopped

🔹 Advantages of Multithreading

  • Better resource utilization: Multiple threads share process resources efficiently.
  • Responsiveness: User interfaces stay responsive while background tasks run.
  • Faster execution: Parallelism on multi-core CPUs.
  • Simplifies program structure: For some problems like servers or real-time apps.

🔹 Challenges of Multithreading

  • Synchronization issues: Data inconsistency due to concurrent access.
  • Deadlocks: Threads waiting indefinitely for each other.
  • Race conditions: Unpredictable results due to timing issues.
  • Thread management overhead: Context switching costs.

📝 Common Exam Questions

  1. What is multithreading?
  2. How do you create a thread in Java?
  3. What are the states in the thread lifecycle?
  4. What are the benefits and challenges of multithreading?

✅ Summary

Topic Key Points
Multithreading Running multiple threads simultaneously
Creating Threads Extend Thread or implement Runnable
Thread Lifecycle New → Runnable → Running → Waiting → Terminated
Benefits Better CPU utilization, responsiveness
Challenges Synchronization, deadlocks, race conditions