UNIT 2 UNIT 3 OBJECT ORIENTED PROGRAMMING

thirunavukkarasu57 10 views 71 slides Mar 05, 2025
Slide 1
Slide 1 of 71
Slide 1
1
Slide 2
2
Slide 3
3
Slide 4
4
Slide 5
5
Slide 6
6
Slide 7
7
Slide 8
8
Slide 9
9
Slide 10
10
Slide 11
11
Slide 12
12
Slide 13
13
Slide 14
14
Slide 15
15
Slide 16
16
Slide 17
17
Slide 18
18
Slide 19
19
Slide 20
20
Slide 21
21
Slide 22
22
Slide 23
23
Slide 24
24
Slide 25
25
Slide 26
26
Slide 27
27
Slide 28
28
Slide 29
29
Slide 30
30
Slide 31
31
Slide 32
32
Slide 33
33
Slide 34
34
Slide 35
35
Slide 36
36
Slide 37
37
Slide 38
38
Slide 39
39
Slide 40
40
Slide 41
41
Slide 42
42
Slide 43
43
Slide 44
44
Slide 45
45
Slide 46
46
Slide 47
47
Slide 48
48
Slide 49
49
Slide 50
50
Slide 51
51
Slide 52
52
Slide 53
53
Slide 54
54
Slide 55
55
Slide 56
56
Slide 57
57
Slide 58
58
Slide 59
59
Slide 60
60
Slide 61
61
Slide 62
62
Slide 63
63
Slide 64
64
Slide 65
65
Slide 66
66
Slide 67
67
Slide 68
68
Slide 69
69
Slide 70
70
Slide 71
71

About This Presentation

NOTES


Slide Content

CS3391-OBEJCT ORIENTED PROGRAMMING UNIT - 3 EXCEPTION HANDLING AND MULTITHREADING Exception Handling basics – Multiple catch Clauses – Nested try Statements – Java’s Built-in Exceptions – User defined Exception. Multithreaded Programming: Java Thread Model–Creating a Thread and Multiple Threads – Priorities – Synchronization – Inter Thread Communication- Suspending –Resuming, and Stopping Threads –Multithreading. Wrappers – Auto boxing. Mr.k.THIRUNAVUKKARASU.,M.E ASsISTANT PROFESSOR DEPArtment of cse tpgit-vellore-02 Unit - III EXCEPTION HANDLING AND MULTITHREADING

Exception Handling Basics Multiple catch Clauses Nested try Statements Java’s Built-in Exceptions User-defined Exceptions Java Thread Model Creating a Thread and Multiple Threads CONTENT LIST <#> Unit - III EXCEPTION HANDLING AND MULTITHREADING Priorities in Threads Synchronization Inter Thread Communication Suspending, Resuming, and Stopping Threads Multithreading Wrapper Classes Auto-boxing and Unboxing

Exception handling is a powerful mechanism in Java that handles runtime errors, ensuring the normal flow of the program is maintained. When an error occurs, an exception object is created, and the Java runtime system (JVM) searches for an appropriate exception handler to process it. Exception: An exception is an event that occurs during the execution of a program that disrupts the normal flow of instructions. Exception Handling Mechanism: Java provides five keywords for exception handling: try catch finally throw throws Exception Handling Basics <#> Unit - III EXCEPTION HANDLING AND MULTITHREADING

<#> Types of Exceptions Unit - III EXCEPTION HANDLING AND MULTITHREADING

<#> How Does JVM Handle an Exception? Unit - III EXCEPTION HANDLING AND MULTITHREADING

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING How Exception Handling Works: try Block: Code that may generate an exception is placed in the try block. catch Block: This block catches the exception and handles it. If an exception occurs in the try block, control transfers to the catch block. finally Block: This block contains code that is always executed, regardless of whether an exception occurred or not. throw: This keyword is used to explicitly throw an exception. throws: This keyword is used in the method signature to declare exceptions that a method can throw.

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING Flow of Exception Handling: Normal Flow: If no exception occurs, all the code inside the try block is executed. The catch block is skipped, and the finally block (if present) is executed. Exception Flow: If an exception occurs, the remaining code in the try block is skipped, and control moves to the corresponding catch block. After that, the finally block is executed (if it exists). No Catch Block: If an exception occurs but no catch block is found to handle it, the program will terminate abnormally.

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING Exception Handling Example: public class ExceptionHandlingExample { publicstaticvoidmain(String[] args) { try { // Code that may generate an exception int data=50 / 0; // This will throw ArithmeticException System.out.println("This line will not be executed due to exception."); } catch (ArithmeticException e) { // Catching the exception and handling it System.out.println("An arithmetic exception occurred: " + e); } finally { // Code that will always be executed System.out.println("Finally block executed."); } System.out.println("Rest of the code..."); } } The code attempts to divide 50 by 0, which triggers an ArithmeticException. The catch block catches the exception and prints the error message. The finally block is executed regardless of the exception.

Types of Exceptions: Checked Exception: Exceptions that are checked at compile-time (e.g., IOException, SQLException). Unchecked Exception: Exceptions that occur at runtime and are not checked during compilation (e.g., ArithmeticException, NullPointerException). <#> Unit - III EXCEPTION HANDLING AND MULTITHREADING Best Practices for Exception Handling Catch Specific Exceptions: Always try to catch specific exceptions instead of catching a generic Exception. Use finally Block for Cleanup: Use the finally block to release resources (like closing files, database connections). Avoid Swallowing Exceptions: Do not use empty catch blocks; always handle the exception properly. Custom Exceptions: Create your own exception classes by extending Exception or RuntimeException when needed for specific business logic.

In Java, you can have multiple catch blocks to handle different types of exceptions that may occur within a single try block. This allows you to catch and handle specific exceptions individually, providing more granular control over the error-handling process. Multiple catch Clauses <#> Unit - III EXCEPTION HANDLING AND MULTITHREADING Key Points about Multiple catch Blocks: Order of Catch Blocks: The order of the catch blocks is important. Java checks the exceptions from top to bottom. More specific exceptions should be caught first, followed by more general exceptions like Exception. Single Try Block: There can be multiple catch blocks associated with a single try block. When an exception occurs, the corresponding catch block is executed, and the remaining catch blocks are skipped.

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING Only One catch Block is Executed: Even if multiple exceptions are thrown, only the first matching catch block will be executed. The Catching Hierarchy: If a superclass exception is caught before a subclass exception, the subclass exception will never be reached. This can cause logical errors in the program, so it’s essential to follow the proper exception hierarchy.

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING public class MultipleCatchExample { public static void main(String[] args) { try { // Code that may generate multiple exceptions int[] numbers = {1, 2, 3}; System.out.println("Array element: " + numbers[3]); // This will throw ArrayIndexOutOfBoundsException int data = 50 / 0; // This will throw ArithmeticException } catch (ArrayIndexOutOfBoundsException e) { // Catch block for ArrayIndexOutOfBoundsException System.out.println("Array index out of bounds exception occurred: " + e); } catch (ArithmeticException e) { // Catch block for ArithmeticException System.out.println("Arithmetic exception occurred: " + e); } catch (Exception e) { // Catch block for any other exceptions System.out.println("Some other exception occurred: " + e); } finally { // Code that will always be executed System.out.println("Finally block executed."); } System.out.println("Rest of the code continues..."); } }

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING Explanation: ArrayIndexOutOfBoundsException: The program tries to access the 4th element of the array (numbers[3]), which does not exist. Hence, an ArrayIndexOutOfBoundsException is thrown. The control jumps to the first catch block that handles the ArrayIndexOutOfBoundsException. The second catch block for ArithmeticException is skipped since the exception is already handled. The finally block is executed no matter what. The remaining part of the program continues after handling the exception.

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING Handling Multiple Exceptions in a Single catch Block (Java 7 and later) Starting from Java 7, you can catch multiple exceptions in a single catch block using a pipe (|). This helps reduce code duplication when handling multiple exceptions with similar handling logic. public class MultiCatchExample { public static void main(String[] args) { try { int[] numbers = {1, 2, 3}; System.out.println(numbers[3]); // This will throw ArrayIndexOutOfBoundsException int data = 50 / 0; // This will throw ArithmeticException } catch (ArrayIndexOutOfBoundsException | ArithmeticException e) { // Common handling for multiple exceptions System.out.println("An exception occurred: " + e); } finally { System.out.println("Finally block executed."); } System.out.println("Rest of the code continues..."); } }

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING Multiple Exceptions in a Single catch Block: Here, both ArrayIndexOutOfBoundsException and ArithmeticException are caught by a single catch block using the pipe (|). This reduces the need for multiple catch blocks if the exception-handling logic is similar. Multiple Catch Blocks: Allow handling different exceptions individually with specific logic. Catch Order: More specific exceptions must be caught before more general exceptions. Single Catch Block (Java 7+): You can catch multiple exceptions in a single catch block using the pipe (|) symbol. finally Block: Always executed after try and catch blocks, whether an exception occurs or not.

In Java, you can nest try statements inside one another. This allows you to handle exceptions at different levels of the application and provides more control over error handling. The outer try block can catch exceptions that occur in any of the inner try blocks, while the inner try blocks can catch their own exceptions. Key Points about Nested Try Statements Exception Propagation: If an exception occurs in an inner try block and is not caught there, it propagates up to the nearest outer catch block. Control Flow: Control flows out of the inner try block when an exception occurs, and the execution continues with the nearest matching catch block. Complexity: While nested try blocks can be useful for handling exceptions at different levels, they can also make code more complex and harder to read. Use them judiciously. Nested Try Statements in Java <#> Unit - III EXCEPTION HANDLING AND MULTITHREADING

Example of Nested Try Statements <#> Unit - III EXCEPTION HANDLING AND MULTITHREADING public class NestedTryExample { public static void main(String[] args) { try { // Outer try block System.out.println("Outer try block started."); int[] numbers = {1, 2, 3}; try { // Inner try block System.out.println("Inner try block started."); System.out.println("Accessing element: " + numbers[5]); // This will throw ArrayIndexOutOfBoundsException } catch (ArrayIndexOutOfBoundsException e) { // Catch block for inner try block System.out.println("Inner catch: Array index out of bounds exception occurred: " + e); } // This code will still execute after the inner catch block int result = 50 / 0; // This will throw ArithmeticException System.out.println("Result: " + result); } catch (ArithmeticException e) { // Catch block for outer try block System.out.println("Outer catch: Arithmetic exception occurred: " + e); } finally { // This block will always execute System.out.println("Finally block executed."); } System.out.println("Rest of the code continues..."); } }

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING Outer Try Block: The program starts with an outer try block that contains an array and an inner try block. It prints a message indicating that the outer try block has started. Inner Try Block: Inside the outer try block, there's an inner try block that attempts to access an out-of-bounds index in the array (numbers[5]). This triggers an ArrayIndexOutOfBoundsException. Inner Catch Block: The inner catch block catches the ArrayIndexOutOfBoundsException, and a message is printed to indicate that this exception has occurred. After the inner catch block is executed, control returns to the outer try block. Remaining Code in Outer Try:

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING The outer try block continues executing the next line, which attempts to perform division by zero (int result = 50 / 0;). This line throws an ArithmeticException. Outer Catch Block: Since the inner catch block has already handled the ArrayIndexOutOfBoundsException, the outer catch block now handles the ArithmeticException. It prints a message indicating that an arithmetic exception occurred. Finally Block: The finally block executes regardless of whether an exception occurred or not, printing that it has been executed. Continuation of the Program: After all exception handling, the program continues executing the remaining code, confirming that the execution flow can proceed even after exceptions are handled.

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING Advantages of Using Nested Try Statements Granularity: Allows handling specific exceptions at different levels of the code, which can lead to more precise error handling. Separation of Concerns: Inner try blocks can encapsulate code that might fail, isolating that failure from the outer logic. Enhanced Readability: In some scenarios, nesting can improve readability by clearly delineating where exceptions can occur. Disadvantages of Using Nested Try Statements Complexity: Nested try blocks can make the code harder to read and understand, especially if overused. Maintenance: More complex structures may lead to difficulties in maintaining the code and understanding the flow of exceptions.

Java’s Built-in Exceptions <#> Unit - III EXCEPTION HANDLING AND MULTITHREADING Java provides a robust exception handling mechanism, and it comes with several built-in exceptions that cater to various error conditions. Understanding these exceptions is crucial for writing robust Java applications. Categories of Built-in Exceptions Java's built-in exceptions can be broadly classified into two categories: Checked Exceptions: These are exceptions that the compiler checks at compile time. If a method could potentially throw a checked exception, it must declare this using the throws keyword, or the exception must be handled with a try-catch block. Examples include: IOException , SQLException , ClassNotFoundException Unchecked Exceptions: These exceptions are not checked at compile time. They are subclasses of RuntimeException and can occur at any point during the program execution. Examples include: NullPointerException , ArrayIndexOutOfBoundsException , ArithmeticException , ClassCastException

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING Exception Hierarchy

Common Built-in Exceptions <#> Unit - III EXCEPTION HANDLING AND MULTITHREADING NullPointerException Description: This exception is thrown when the Java Virtual Machine (JVM) attempts to access an object or call a method on an object that is null. public class NullPointerExample { public static void main(String[] args) { String str = null; System.out.println(str.length()); // This will throw NullPointerException } }

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING ArithmeticException This exception is thrown when an exceptional arithmetic condition has occurred, such as dividing by zero. public class ArithmeticExample { public static void main(String[] args) { int result = 10 / 0; // This will throw ArithmeticException System.out.println(result); } }

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING ArrayIndexOutOfBoundsException This exception is thrown when an array has been accessed with an illegal index (either negative or greater than or equal to the size of the array). public class ArrayExample { public static void main(String[] args) { int[] arr = {1, 2, 3}; System.out.println(arr[3]); // This will throw ArrayIndexOutOfBoundsException } }

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING ClassCastException This exception occurs when an object is cast to a subclass of which it is not an instance. public class ClassCastExample { public static void main(String[] args) { Object obj = new String("Hello"); Integer num = (Integer) obj; // This will throw ClassCastException } }

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING IOException This is a checked exception that occurs when there is a failure during input or output operations. import java.io.*; public class IOExceptionExample { public static void main(String[] args) { try { FileReader file = new FileReader("nonexistentfile.txt"); // This will throw IOException BufferedReader br = new BufferedReader(file); br.close(); } catch (IOException e) { System.out.println("IOException occurred: " + e.getMessage()); } } }

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING SQLException This is a checked exception that provides information about database access errors or other errors related to SQL operations. import java.sql.*; public class SQLExceptionExample { public static void main(String[] args) { try { Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "user", "password"); Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("SELECT * FROM nonexistenttable"); // This may throw SQLException } catch (SQLException e) { System.out.println("SQLException occurred: " + e.getMessage()); } } }

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING ClassNotFoundException This checked exception is thrown when an application tries to load a class through its string name but the class cannot be found. public class ClassNotFoundExample { public static void main(String[] args) { try { Class.forName("com.example.NonExistentClass"); // This will throw ClassNotFoundException } catch (ClassNotFoundException e) { System.out.println("Class not found: " + e.getMessage()); } } }

Exception Type NullPointerException Unchecked Occurs when trying to access an object with null. ArithmeticException Unchecked Occurs during an arithmetic operation, such as division by zero. ArrayIndexOutOfBoundsException Unchecked Occurs when accessing an invalid array index. ClassCastException Unchecked Occurs when casting an object to an incompatible class. IOException Checked Occurs during input/output operations. SQLException Checked Occurs during SQL operations with databases. ClassNotFoundException Checked Occurs when a class cannot be found during runtime. <#> Unit - III EXCEPTION HANDLING AND MULTITHREADING

User-defined exceptions in Java allow developers to create their own custom exception classes, which can be thrown and caught in a manner similar to built-in exceptions. This capability is particularly useful when you want to represent specific error conditions relevant to your application that aren't covered by standard exceptions. User-defined Exceptions <#> Unit - III EXCEPTION HANDLING AND MULTITHREADING When to Use User-Defined Exceptions Specific Business Logic: When certain conditions or errors need to be represented that are specific to your application’s business logic. Clarity: To improve code readability and clarity by using meaningful names for exceptions that convey the nature of the error. Error Handling: To provide more precise error handling, allowing you to separate different error conditions more effectively.

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING Steps to Create a User-Defined Exception Extend the Exception Class: Create a new class that extends Exception (or RuntimeException for unchecked exceptions). Define Constructors: Implement constructors for your exception class to initialize error messages and other relevant information. Throw the Exception: Use the throw keyword to throw your custom exception when a specific error condition occurs. Catch the Exception: Use a try-catch block to handle the custom exception where appropriate.

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING Example of a User-Defined Exception Let’s create a user-defined exception called InvalidAgeException that will be thrown when an invalid age is provided (e.g., a negative age or an age that is unrealistically high). Step 1: Create the User-Defined Exception Class // InvalidAgeException.java public class InvalidAgeException extends Exception { // Constructor that accepts a message public InvalidAgeException(String message) { super(message); // Call the constructor of Exception class } }

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING Step 2: Use the Exception in Your Code // AgeValidator.java public class AgeValidator { // Method to validate age public static void validateAge(int age) throws InvalidAgeException { if (age < 0 || age > 150) { throw new InvalidAgeException("Age must be between 0 and 150."); } else { System.out.println("Age is valid: " + age); } } public static void main(String[] args) { try { validateAge(200); // This will throw InvalidAgeException } catch (InvalidAgeException e) { System.out.println("Caught exception: " + e.getMessage()); } try { validateAge(25); // This will not throw an exception } catch (InvalidAgeException e) { System.out.println("Caught exception: " + e.getMessage()); } }}

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING Explanation of the Example Custom Exception Class (InvalidAgeException): The InvalidAgeException class extends Exception, making it a checked exception. It has a constructor that accepts a message and passes it to the superclass constructor (Exception). Age Validation Method: The validateAge(int age) method checks if the provided age is valid (between 0 and 150). If the age is invalid, it throws an instance of InvalidAgeException. Main Method: The main method attempts to validate two ages. The first attempt with an age of 200 throws InvalidAgeException, which is caught in the catch block, and an error message is printed. The second attempt with an age of 25 successfully validates, printing that the age is valid.

Advantages of User-Defined Exceptions Descriptive Errors: Custom exceptions allow for more descriptive error messages, which can help in debugging. Granular Control: You can have specific exception types for different error conditions, allowing for more granular control over exception handling. Encapsulation of Business Logic: They help encapsulate business logic and can enforce specific constraints or rules. <#> Unit - III EXCEPTION HANDLING AND MULTITHREADING

Best Practices for User-Defined Exceptions Meaningful Names: Name your exception classes clearly to reflect the error condition they represent. Documentation: Document your custom exceptions well to explain when and why they should be thrown. Use Checked vs. Unchecked Appropriately: Use checked exceptions for recoverable conditions and unchecked exceptions for programming errors or runtime conditions. Extend the Right Class: Extend Exception for checked exceptions and RuntimeException for unchecked exceptions based on your use case. <#> Unit - III EXCEPTION HANDLING AND MULTITHREADING

Java's thread model is built around the concept of concurrent execution. A thread in Java is the smallest unit of a program that can execute independently. Java provides built-in support for multithreading, enabling efficient CPU utilization and concurrent execution of tasks. In Java, multithreading is managed using the java.lang.Thread class or by implementing the java.lang.Runnable interface. Java Thread Model <#> Unit - III EXCEPTION HANDLING AND MULTITHREADING

Key Concepts of the Java Thread Model Thread: A thread is a lightweight subprocess. Java allows you to run multiple threads concurrently, with each thread having its own execution path. Concurrency: Multiple threads are executed simultaneously, sharing CPU resources, and improving application performance. Life Cycle of a Thread: New: When a thread is created but not started. Runnable: After the thread's start() method is invoked, it becomes runnable. The thread is ready to run, waiting for CPU allocation. Running: When the thread is executing. Blocked/Waiting: A thread enters this state when it's waiting for a resource or a signal from another thread. Terminated: Once the thread completes its task or exits due to an exception, it enters the terminated state. Thread Priorities: Each thread has a priority (default is NORM_PRIORITY = 5). Threads with higher priority are more likely to be selected for execution by the thread scheduler. <#> Unit - III EXCEPTION HANDLING AND MULTITHREADING

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING Creating a Thread in Java There are two ways to create a thread in Java: Extending the Thread Class: A class can extend the Thread class and override its run() method to define the task for the thread. Implementing the Runnable Interface: A class can implement the Runnable interface and pass the instance to a Thread object.

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING // Example using Thread class class MyThread extends Thread { public void run() { for (int i = 1; i <= 5; i++) { System.out.println(Thread.currentThread().getName() + ": " + i); try { Thread.sleep(500); // Sleep for 500 milliseconds } catch (InterruptedException e) { System.out.println(e); } } } } public class ThreadExample { public static void main(String[] args) { MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); // Start the threads t1.start(); t2.start(); } }

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING Thread Creation: MyThread extends the Thread class and overrides the run() method to print numbers from 1 to 5. Starting Threads: Two threads, t1 and t2, are created and started by calling the start() method. This invokes the run() method concurrently in separate threads. Thread Sleep: Thread.sleep(500) pauses each thread for 500 milliseconds between iterations, making it easier to observe thread interleaving. Output: Both threads print numbers in an interleaved manner, demonstrating concurrent execution. The actual order may vary depending on the thread scheduler.

Implementing the Runnable Interface <#> Unit - III EXCEPTION HANDLING AND MULTITHREADING // Example using Runnable interface class MyRunnable implements Runnable { public void run() { for (int i = 1; i <= 5; i++) { System.out.println(Thread.currentThread().getName() + ": " + i); try { Thread.sleep(500); // Sleep for 500 milliseconds } catch (InterruptedException e) { System.out.println(e); } } }} public class RunnableExample { public static void main(String[] args) { // Create instances of Runnable MyRunnable runnable1 = new MyRunnable(); MyRunnable runnable2 = new MyRunnable(); // Pass the Runnable to the Thread object Thread t1 = new Thread(runnable1); Thread t2 = new Thread(runnable2); // Start the threads t1.start(); t2.start(); }}

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING Runnable Interface: MyRunnable implements the Runnable interface and defines the task inside the run() method. Thread Creation: To create a thread, we pass an instance of MyRunnable to a Thread object (t1 and t2). Thread Start: The start() method is called on t1 and t2 to start the execution of their respective run() methods concurrently. Thread Execution: Similar to the previous example, the threads print numbers in an interleaved manner.

T he life cycle of a Java thread with a brief description of each state: New: The thread is created but has not yet started. This is the state after a thread object is instantiated, but before start() is called. Example: Thread t = new Thread(); Runnable: After the thread’s start() method is invoked, the thread enters the runnable state, where it is ready to run but waiting for CPU allocation. Example: t.start(); Running: When the thread scheduler selects the thread, it transitions from the runnable state to the running state, executing the run() method. Blocked/Waiting: A thread can enter the blocked or waiting state when it is waiting for resources (like I/O or locks) or sleeping for a specified time. This can happen due to Thread.sleep() or wait(). Terminated: The thread terminates once it finishes executing the run() method, or if an uncaught exception causes it to exit prematurely. Example: The thread completes its task or throws an exception that is not handled. Life Cycle of a Thread in Java <#> Unit - III EXCEPTION HANDLING AND MULTITHREADING

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING Thread Priorities In Java, each thread is assigned a priority. The thread scheduler uses these priorities to decide which thread should be executed first, although this behavior may vary across different JVM implementations. Thread.MIN_PRIORITY = 1 Thread.NORM_PRIORITY = 5 (default) Thread.MAX_PRIORITY = 10

Synchronization in Java <#> Unit - III EXCEPTION HANDLING AND MULTITHREADING Synchronization in Java is a mechanism to control access to shared resources by multiple threads. When multiple threads attempt to access a shared resource (such as a variable, object, or file), synchronization ensures that only one thread can access the resource at a time, preventing race conditions and inconsistent data. In a multithreaded environment, if two or more threads execute concurrently, it can lead to unexpected results if they access the same resource without proper synchronization. Java provides built-in support for synchronization to manage this kind of situation.

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING Why Do We Need Synchronization? Consistency: To maintain data consistency when multiple threads access a shared resource simultaneously. Thread Safety: Synchronization ensures thread safety by allowing only one thread to access a critical section of code at any given time. Avoiding Race Conditions: Race conditions occur when two or more threads attempt to change shared data at the same time, leading to unpredictable results. Synchronization avoids this.

Process Synchronization: Ensuring multiple processes don’t interfere with each other while accessing shared resources (handled by the operating system). Thread Synchronization: Ensuring multiple threads within a process don’t interfere with each other when accessing shared resources (handled in Java using the synchronization mechanism). <#> Unit - III EXCEPTION HANDLING AND MULTITHREADING Types of Synchronization

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING How Java Synchronization Works Java provides several ways to synchronize code: Synchronized Methods: Synchronizing an entire method. Synchronized Blocks: Synchronizing a specific block of code within a method. Static Synchronization: Synchronizing static methods to control access to static variables.

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING Synchronized Methods A synchronized method ensures that only one thread can execute it at a time for a particular object. public synchronized void methodName() { // Critical section of code }

example <#> Unit - III EXCEPTION HANDLING AND MULTITHREADING class Counter { private int count = 0; // Synchronized method to ensure thread safety public synchronized void increment() { count++; } public int getCount() { return count; } } public class SynchronizedMethodExample { public static void main(String[] args) throws InterruptedException { Counter counter = new Counter(); // Create two threads 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(); } }); // Start both threads t1.start(); t2.start(); // Wait for both threads to finish t1.join(); t2.join(); // Print the final count value System.out.println("Final count: " + counter.getCount()); } }

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING The increment() method is marked as synchronized, which ensures that only one thread can execute it at a time. Without synchronization, multiple threads might interfere with each other, leading to incorrect count values (race condition). In this example, two threads increment the count 1000 times each, and the final count is 2000.

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING Synchronized Blocks If you don't want to synchronize the entire method and only need to synchronize a part of the method (critical section), you can use synchronized blocks. Syntax : synchronized(object) { // Critical section of code }

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING class Counter { private int count = 0; // Method containing a synchronized block public void increment() { synchronized (this) { // Synchronizing on the current object count++; } } public int getCount() { return count; }} public class SynchronizedBlockExample { public static void main(String[] args) throws InterruptedException { Counter counter = new Counter(); // Create two threads 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(); } }); // Start both threads t1.start(); t2.start(); // Wait for both threads to finish t1.join(); t2.join(); // Print the final count value System.out.println("Final count: " + counter.getCount()); } }

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING Static Synchronization When static methods are synchronized, the synchronization is on the class level, not on the instance level. This means the lock applies to the entire class, not just to individual objects. class Counter { private static int count = 0; // Static synchronized method public static synchronized void increment() { count++; } public static int getCount() { return count; }}public class StaticSynchronizationExample { public static void main(String[] args) throws InterruptedException { // Create two threads 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(); } }); // Start both threads t1.start(); t2.start(); // Wait for both threads to finish t1.join(); t2.join(); // Print the final count value System.out.println("Final count: " + Counter.getCount()); }}

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING Locks in Java (Advanced Synchronization) While synchronized methods or blocks are easy to use, Java also provides more flexible mechanisms for thread synchronization: ReentrantLock: A lock with the same behavior as implicit locks provided by synchronized, but it has additional features like fairness and the ability to try acquiring a lock without blocking indefinitely.

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING import java.util.concurrent.locks.ReentrantLock; class Counter { private int count = 0; private ReentrantLock lock = new ReentrantLock(); public void increment() { lock.lock(); // Acquire the lock try { count++; } finally { lock.unlock(); // Release the lock } } public int getCount() { return count; }} public class ReentrantLockExample { public static void main(String[] args) throws InterruptedException { Counter counter = new Counter(); // Create two threads 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(); } }); // Start both threads t1.start(); t2.start(); // Wait for both threads to finish t1.join(); t2.join(); // Print the final count value System.out.println("Final count: " + counter.getCount()); }}

Inter-thread communication in Java allows multiple threads to coordinate their actions with each other. It is a process by which threads can communicate and cooperate with each other, especially in cases where one thread's task depends on the completion or progress of another thread's task. Java provides a robust and straightforward way to achieve this via three methods of the Object class: wait() notify() notifyAll() These methods are part of every Java object, as they are inherited from the Object class, and they are used for thread communication within a synchronized context. Inter-Thread Communication in Java <#> Unit - III EXCEPTION HANDLING AND MULTITHREADING

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING Why Do We Need Inter-Thread Communication? Consider a scenario where: One thread produces some data, and another thread consumes that data. The consumer should wait until the producer has produced the required data. The producer should stop producing when the consumer is consuming, to avoid resource wastage. Without proper communication between threads, the consumer might end up trying to consume before the producer is ready, or the producer might generate excess data that the consumer can't handle immediately. This is where inter-thread communication comes into play.

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING The Role of wait(), notify(), and notifyAll() wait(): Causes the current thread to wait until another thread invokes notify() or notifyAll() on the same object. notify(): Wakes up one thread waiting on the object's monitor (if any). The awakened thread can only proceed when it gets the lock on the object. notifyAll(): Wakes up all threads waiting on the object's monitor, but only one of them will be able to acquire the lock and proceed. These methods must be called within a synchronized block or method. The thread calling wait() releases the lock it holds on the object and moves to the waiting state. Once notified by notify() or notifyAll(), the thread moves from the waiting state to the runnable state, but it must reacquire the object's lock before continuing.

Let’s consider a classical Producer-Consumer problem where the producer produces some data, and the consumer consumes the data. If the buffer is empty, the consumer waits for the producer to produce data, and if the buffer is full, the producer waits for the consumer to consume the data. Example: Producer-Consumer Problem Using Inter-Thread Communication <#> Unit - III EXCEPTION HANDLING AND MULTITHREADING

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING class SharedResource { private int data; private boolean hasData = false; // Method called by the producer to produce data public synchronized void produce(int value) throws InterruptedException { // If data is already present, wait until it's consumed while (hasData) { wait(); } data = value; System.out.println("Produced: " + data); hasData = true; // Notify the consumer that data is available notify(); } // Method called by the consumer to consume data public synchronized void consume() throws InterruptedException { // If no data is present, wait until the producer produces while (!hasData) { wait(); } System.out.println("Consumed: " + data); hasData = false; // Notify the producer that data has been consumed notify(); } } class Producer extends Thread { private SharedResource sharedResource; public Producer(SharedResource sharedResource) { this.sharedResource = sharedResource; } public void run() { for (int i = 1; i <= 5; i++) { try { sharedResource.produce(i); Thread.sleep(500); // Simulating time taken to produce } catch (InterruptedException e) { e.printStackTrace(); } } }}

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING class Consumer extends Thread { private SharedResource sharedResource; public Consumer(SharedResource sharedResource) { this.sharedResource = sharedResource; } public void run() { for (int i = 1; i <= 5; i++) { try { sharedResource.consume(); Thread.sleep(1000); // Simulating time taken to consume } catch (InterruptedException e) { e.printStackTrace(); } } } } public class ProducerConsumerExample { public static void main(String[] args) { SharedResource sharedResource = new SharedResource(); Producer producer = new Producer(sharedResource); Consumer consumer = new Consumer(sharedResource); producer.start(); consumer.start(); } }

<#> Unit - III EXCEPTION HANDLING AND MULTITHREADING SharedResource Class: Contains two synchronized methods produce() and consume(). The producer thread calls produce() to produce data, and the consumer thread calls consume() to consume the data. The boolean flag hasData indicates whether the resource has been produced or not. The producer waits if the resource is already produced (hasData == true), and the consumer waits if no resource is produced (hasData == false). wait() Method: The wait() method is used by the producer to wait if the data has already been produced and by the consumer to wait if there is no data to consume. notify() Method: The notify() method is called by the producer after producing data to notify the consumer that data is available. Similarly, the consumer calls notify() after consuming data to notify the producer to produce more data. Producer and Consumer Threads: The Producer and Consumer classes extend the Thread class. They continuously produce and consume data within their loops.

wait(): When a thread calls the wait() method, it releases the lock on the object and goes into a waiting state. The thread can be awakened only when another thread calls notify() or notifyAll() on the same object. notify(): When a thread calls the notify() method, it wakes up a single thread that is waiting on that object’s monitor. If multiple threads are waiting, one of them is chosen arbitrarily to wake up. notifyAll(): When a thread calls the notifyAll() method, it wakes up all threads waiting on that object’s monitor. The awakened threads compete to get the object’s lock, and only one of them will successfully acquire it. How wait(), notify(), and notifyAll() Work <#> Unit - III EXCEPTION HANDLING AND MULTITHREADING

Must Be Inside a Synchronized Block: Both wait(), notify(), and notifyAll() must be called inside a synchronized block or synchronized method. If they are called outside a synchronized context, the program will throw IllegalMonitorStateException. Locks and Monitors: The wait() method releases the lock on the object before it waits and re-acquires the lock when it's notified and moves to the runnable state. The lock is important because it ensures that no other thread can modify the object’s state while another thread is waiting. Spurious Wakeups: It's possible for threads to wake up from wait() even if they haven't been notified. This is known as a spurious wakeup. To handle this, always call wait() inside a loop that checks the condition you’re waiting for, as shown in the example: Important Points About Inter-Thread Communication <#> Unit - III EXCEPTION HANDLING AND MULTITHREADING while (conditionIsNotMet) { wait(); }

Performance: Using notifyAll() wakes up all waiting threads, which may lead to unnecessary competition for resources. Prefer notify() when only one thread needs to be woken up, and use notifyAll() when multiple threads need to be notified. <#> Unit - III EXCEPTION HANDLING AND MULTITHREADING Thread Cooperation Using Locks (Alternative Approach) Another common approach for inter-thread communication in Java is using advanced locking mechanisms from the java.util.concurrent.locks package, such as ReentrantLock combined with Condition. These mechanisms provide a more flexible way to control thread synchronization and communication.

In Java, suspending, resuming, and stopping threads can be done by controlling the thread's flow with flags or interrupt mechanisms. Directly stopping or suspending threads was possible with methods like suspend(), resume(), and stop() in earlier Java versions. However, these methods have been deprecated because they are unsafe and could lead to deadlock or resource inconsistencies. Suspending, Resuming, and Stopping Threads <#> Unit - III EXCEPTION HANDLING AND MULTITHREADING Why suspend(), resume(), and stop() Are Deprecated suspend(): When a thread is suspended, it holds any locks it has acquired. If it’s waiting for another thread to release a resource, a deadlock could occur. resume(): If not carefully managed, resuming could release a suspended thread before it’s ready, potentially leading to data corruption. stop(): The abrupt stopping of a thread does not release resources properly, which can leave resources in an inconsistent state and lead to resource leaks.

The modern approach is to use flags, interrupts, or volatile variables to safely control a thread’s execution. Alternative Approaches Suspending Threads: Using flags or wait() and notify(). Resuming Threads: Changing flags or calling notify(). Stopping Threads: Using a boolean flag or interrupt(). <#> Unit - III EXCEPTION HANDLING AND MULTITHREADING
Tags