Exception Handling & Multithreaded Programming
Exception handling fundamentals:-
Exception handling in Java is a mechanism used to handle runtime
errors so that the normal flow of a program is not disrupted. Java
provides a robust and object-oriented way to handle exceptions using
the try, catch, finally, throw, and throws keywords.
1. What is an Exception?
An exception is an event that occurs during the execution of a program
that disrupts the normal flow of instructions. It can be caused by various
factors, such as invalid input, file not found, division by zero, or network
failures.
Types of Exceptions in Java
1.Checked Exceptions
1. These are exceptions that are checked at compile time.
2. The programmer must handle them using a try-catch
block or declare them using throws.
3. Examples: IOException, SQLException.
2.Unchecked Exceptions (Runtime Exceptions)
1. These exceptions occur at runtime and are not checked at
compile time.
2. They usually indicate programming errors, such as logical
mistakes or improper API usage.
3. Examples: NullPointerException,
ArithmeticException,
ArrayIndexOutOfBoundsException.
3.Errors
1. These are serious problems that should not be handled
using exception handling mechanisms.
2. Examples: OutOfMemoryError,
StackOverflowError.
2. Exception Handling Keywords
Java provides five key constructs for handling exceptions:
1. try Block
The try block contains the code that might throw an exception.
try {
int result = 10 / 0; // This will cause ArithmeticException
}
2. catch Block
The catch block is used to handle the exception thrown in the try block.
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
System.out.println("Cannot divide by zero!");
}
3. finally Block
The finally block contains code that will always execute, whether an exception
occurs or not. It is commonly used to close resources like database connections or file
streams.
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
System.out.println("Exception caught.");
} finally {
System.out.println("Finally block executed.");
}
4. throw Keyword
The throw keyword is used to explicitly throw an exception.
public void checkAge(int age) {
if (age < 18) {
throw new ArithmeticException("Age must be 18 or above.");
}
}
5. throws Keyword
The throws keyword is used to declare an exception that a method
might throw.
public void readFile() throws IOException {
FileReader file = new FileReader("test.txt");
}
2. Multiple catch Blocks
You can have multiple catch blocks to handle different
types of exceptions.
try {
int arr[] = new int[5];
arr[10] = 30; // ArrayIndexOutOfBoundsException
} catch (ArithmeticException e) {
System.out.println("Arithmetic Exception");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Array Index is out of bounds");
}
4. Custom Exceptions
You can create your own exception class by extending the Exception
class.
class MyException extends Exception {
public MyException(String message) {
super(message);
}
}
public class CustomExceptionDemo {
public static void main(String[] args) {
try {
throw new MyException("Custom Exception
Occurred");
} catch (MyException e) {
System.out.println(e.getMessage());
}
}
}
Creating Custom Exception Subclasses in
Java
Java allows developers to define their own exceptions by creating custom
exception classes. This is useful when the built-in exception types do not
adequately describe a particular error situation in an application.
1. Steps to Create a Custom Exception
To create a custom exception:
1. Extend either Exception (for a checked exception) or
RuntimeException (for an unchecked exception).
2. Provide a constructor that takes a message.
3. Optionally, include additional constructors or methods to enhance
functionality.
2. Example: Creating a Custom Checked
Exception
A checked exception requires explicit handling using try-catch or
throws.
// Step 1: Define a custom exception class by extending Exception
class InvalidAgeException extends Exception {
// Constructor to accept an error message
public InvalidAgeException(String message) {
super(message);
}
}
// Step 2: Using the custom exception
public class CustomExceptionExample {
// Method that throws the custom exception
public static void validateAge(int age) throws InvalidAgeException {
if (age < 18) {
throw new InvalidAgeException("Age must be 18 or older.");
} else {
System.out.println("Valid age.");
}
}
public static void main(String[] args) {
try {
validateAge(16); // This will throw an exception
} catch (InvalidAgeException e) {
System.out.println("Exception caught: " + e.getMessage());
}
}
}
Output:-
Exception caught: Age must be 18 or older.
Since InvalidAgeException extends Exception, it is a checked exception, meaning
the compiler forces us to handle it using try-catch or declare it with throws.
3. Example: Creating a Custom
Unchecked Exception
An unchecked exception extends RuntimeException and does not
require explicit handling.
// Step 1: Define an unchecked exception class
class NegativeNumberException extends RuntimeException {
public NegativeNumberException(String message) {
super(message);
}
}
// Step 2: Using the custom unchecked exception
public class CustomRuntimeException {
public static void checkNumber(int num) {
if (num < 0) {
throw new NegativeNumberException("Number cannot be
negative!");
}
System.out.println("Valid number: " + num);
}
public static void main(String[] args) {
checkNumber(10); // No exception
checkNumber(-5); // This will throw an exception
}}
Output:-
Valid number: 10
Exception in thread "main" NegativeNumberException: Number cannot
be negative!
Since NegativeNumberException extends
RuntimeException, it is an unchecked exception and does
not require throws or try-catch unless the programmer
wants to handle it explicitly.
The Java Thread Model:-
Java provides a robust multithreading model that allows programs to run
multiple threads simultaneously, improving performance and
responsiveness. This is particularly useful in applications that require
concurrent execution, such as web servers, gaming applications, and real-
time systems.
1. What is a Thread?
A thread is the smallest unit of execution in a process. Each
Java program runs at least one thread (the main thread), but
additional threads can be created to perform tasks in parallel.
Single-threaded vs. Multi-threaded Execution
● Single-threaded: Executes one task at a time.
● Multi-threaded: Runs multiple tasks concurrently,
improving efficiency.
2. Creating Threads in Java
Java provides two ways to create a thread:
1. Extending the Thread Class
class MyThread extends Thread {
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println("Thread running: " + i);
}
}
}
public class ThreadExample {
public static void main(String[] args) {
MyThread thread1 = new MyThread();
thread1.start(); // Starts the thread
}
}
2. Implementing the Runnable Interface
class MyRunnable implements Runnable {
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println("Runnable
running: " + i);
}
}
}
public class RunnableExample {
public static void main(String[] args) {
Thread thread2 = new Thread(new
MyRunnable());
thread2.start();
}
}
3. Thread Lifecycle
A thread in Java goes through different states:
1. New – Created but not started (new Thread()).
2. Runnable – Ready to run, waiting for CPU (start() called).
3. Running – Currently executing.
4. Blocked – Waiting for a resource (like I/O).
5. Waiting/Timed Waiting – Waiting indefinitely or for a fixed time.
6. Terminated – Finished execution.
Example Demonstrating Lifecycle
class MyThread extends Thread {
public void run() {
System.out.println("Thread is
running...");
}
}
public class ThreadLifecycle {
public static void main(String[] args) {
MyThread t = new MyThread();
System.out.println("Thread State: " +
t.getState()); // NEW
t.start();
System.out.println("Thread State: " +
t.getState()); // RUNNABLE
}
}
4. Thread Methods
Method Description
start() Starts the thread.
run() Defines the thread's execution logic.
sleep(m
Pauses execution for specified time.
s)
Makes one thread wait for another to
join()
complete.
isAlive() Checks if the thread is still running.
yield() Allows other threads to execute first.
Example: Using sleep() and join()
class MyThread extends Thread {
public void run() {
try {
for (int i = 1; i <= 3; i++) {
System.out.println(Thread.currentThread().getName() + "
running: " + i);
Thread.sleep(1000); // Pauses execution for 1 second
}
} catch (InterruptedException e) {
System.out.println("Thread interrupted!");
}
}
}
public class ThreadMethods {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.start();
t1.join(); // Ensures t1 completes before t2 starts
t2.start();
}
}
5. Synchronization in Threads
Problem of Race Conditions
If multiple threads modify the same resource simultaneously, race
conditions occur.
Solution: Synchronized Methods
class SharedResource {
synchronized void printNumbers() { // Synchronized method
for (int i = 1; i <= 3; i++) {
System.out.println(Thread.currentThread().getName() + " prints: "
+ i);
}
}
}
class MyThread extends Thread {
SharedResource obj;
MyThread(SharedResource obj) {
this.obj = obj;
}
public void run() {
obj.printNumbers();
}
}
public class SyncExample {
public static void main(String[] args) {
SharedResource obj = new SharedResource();
MyThread t1 = new MyThread(obj);
MyThread t2 = new MyThread(obj);
t1.start();
t2.start();
}
}
Suspending, Resuming, and Stopping
Threads in Java
Java provides different ways to suspend (pause), resume (continue), and
stop (terminate) threads. However, the older methods like suspend(),
resume(), and stop() from the Thread class are deprecated due to
safety concerns. Instead, Java provides safer alternatives using flags,
wait(), notify(), and interrupts.
1. Suspending and Resuming Threads
(Deprecated Methods)
Java originally provided suspend() and resume(), but they are now
deprecated due to deadlock risks.
class MyThread extends Thread {
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().g
etName() + " - Count: " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Thread
interrupted.");
}
}
}
}
public class OldSuspendResumeExample {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start();
try {
Thread.sleep(3000);
t1.suspend(); // Deprecated
System.out.println("Thread
suspended.");
Thread.sleep(3000);
t1.resume(); // Deprecated
System.out.println("Thread resumed.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Problem: suspend() holds a thread indefinitely, which
can cause deadlocks.
Stopping a Thread (Deprecated stop()
Method)
The stop() method is deprecated because it can terminate a thread
immediately, leaving shared resources in an inconsistent state.
class MyThread extends Thread {
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + " -
Count: " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Thread interrupted.");
}
}
}
}
public class OldStopExample {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start();
try {
Thread.sleep(3000);
t1.stop(); // Deprecated
System.out.println("Thread stopped.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}