java unit-3
java unit-3
The Exception Handling in Java is one of the powerful mechanisms to handle the runtime
errors so that the normal flow of the application can be maintained.
In Java, an exception is an event that disrupts the normal flow of the program. It is an object
which is thrown at runtime.
1. statement 1;
2. statement 2;
3. statement 3;
4. statement 4;
5. statement 5;//exception occurs
6. statement 6;
7. statement 7;
8. statement 8;
9. statement 9;
10. statement 10;
Suppose there are 10 statements in a Java program and an exception occurs at statement 5;
the rest of the code will not be executed, i.e., statements 6 to 10 will not be executed.
However, when we perform exception handling, the rest of the statements will be executed.
That is why we use exception handling in Java.
1. Checked Exception
2. Unchecked Exception
3. Error
1) Checked Exception
The classes that directly inherit the Throwable class except Runtime Exception and Error are
known as checked exceptions. For example, IOException, SQLException, etc. Checked
exceptions are checked at compile-time.
1. Checked Exceptions
Checked exceptions are the exceptions that are checked at compile-time. This means that the
compiler verifies that the code handles these exceptions either by catching them or declaring
them in the method signature using the throws keyword. Examples of checked exceptions
include:
2) Unchecked Exception
The classes that inherit the Runtime Exception are known as unchecked exceptions. For
example, ArithmeticException, NullPointerException, ArrayIndexOutOfBoundsException,
etc. Unchecked exceptions are not checked at compile-time, but they are checked at
runtime.
Forced Handling: Checked exceptions enforce explicit handling, either by catching them or
declaring them to be thrown. This helps in improving code reliability and robustness.
Optional Handling: Handling of unchecked exceptions is optional. While it's good practice
to handle them for robustness, it's not mandatory.
3) Error
Error is irrecoverable. Some example of errors are OutOfMemoryError, Virtual Machine
Error etc.
Critical Conditions: Errors usually indicate critical conditions, such as JVM failures or
system resource exhaustion, where the application cannot recover.
Java provides five keywords that are used to handle the exception.
Common Scenarios of Java Exceptions
There are given some scenarios where unchecked exceptions may occur. They are as follows:
int a=50/0;//ArithmeticException
String s=null;
System.out.println(s.length());//NullPointerException
String s="abc";
int i=Integer.parseInt(s);//NumberFormatException
4) A scenario where ArrayIndexOutOfBoundsException occurs
When an array exceeds to it's size, the ArrayIndexOutOfBoundsException occurs. there may
be other reasons to occur ArrayIndexOutOfBoundsException. Consider the following
statements.
If an exception occurs at the particular statement in the try block, the rest of the block code
will not execute. So, it is recommended not to keep the code in try block that will not throw
an exception.
Example 1
TryCatchExample1.java
Output:
As displayed in the above example, the rest of the code is not executed (in such case,
the rest of the code statement is not printed).
There might be 100 lines of code after the exception. If the exception is not handled, all the
code below the exception won't be executed.
Example 2
TryCatchExample2.java
Output:
java.lang.ArithmeticException: / by zero
rest of the code
As displayed in the above example, the rest of the code is executed, i.e., the rest of the
code statement is printed.
1. try {
2. // Code that may throw an exception
3. } catch (IOException e) {
4. // Handle IOException
5. } catch (NumberFormatException e) {
6. // Handle NumberFormatException
7. } catch (Exception e) {
8. // Handle any other exceptions
9. }
NumberFormatExceptionExample.java
ArrayIndexOutOfBoundsExceptionExample.java
If the called function is unable to handle the exception ,it can rethrow the exception and it
can be handled by the calling function.
class ThrowEx{
static void fun()
{
try
{
throw new NullPointerException("demo");
}
catch (NullPointerException e)
{
System.out.println("Caught inside fun().");
throw e; // rethrowing the exception
}
}
public static void main(String args[])
{
try {
fun();
}
catch (NullPointerException e)
{
System.out.println("Caught in main.");
}
}
}
Output
Caught inside fun().
Caught in main.
THROWS:
class ThrowsExecp
{
static void fun() throws IllegalAccessException
{
System.out.println("Inside fun(). ");
throw new IllegalAccessException("demo");
}
public static void main(String args[])
{
try
{
fun();
}
catch (IllegalAccessException e)
{
System.out.println("caught in main.");
}
}
}
Output
Inside fun().
caught in main
MULTITHREADING:
1. Effective Resource Sharing: Data sharing and interthread communication are made
possible by the fact that threads operating in the same process share the same
memory.
2. Faster Context Switching: Multithreading is better appropriate for jobs that need
frequent thread switching since context switching between threads is usually faster
than between processes.
3. Low Overhead: Compared to processes, creating and managing threads is typically
lighter and has less overhead.
Drawbacks of Multithreading
Multithreading.java
Thread 11 - Count: 1
Thread 10 - Count: 1
Thread 11 - Count: 2
Thread 10 - Count: 2
Thread 11 - Count: 3
Thread 10 - Count: 3
Multiprocessing
The method of running several processes concurrently, each with its own memory space, is
known as multiprocessing. Multiprocessing in Java can be accomplished by the creation of
numerous Java Virtual Machines (JVMs) or by using third-party multiprocessing tools, such
as the Java ProcessBuilder.
1. Symmetric Multiprocessing (SMP): One processor runs the operating system, while
the other processors are used to run user programs.
2. Asymmetric Multiprocessing: Any available processor can run the operating system,
or all processors can run user programs concurrently.
1. Memory Isolation: Greater fault tolerance and isolation are provided by each process
operating in its memory region. One process crashing doesn't impact other processes.
2. Optimal CPU Utilisation: Multiprocessing is appropriate for CPU-bound workloads
as it effectively uses several CPU cores.
3. Simplified Synchronisation: By not sharing memory, processes may synchronize
more easily and prevent race situations.
Drawbacks of Multiprocessing
Less memory use and resource Higher memory use and resource
Overhead
overhead. overhead.
A thread is a program in execution created to perform a specific task. Life cycle of a Java
thread starts with its birth and ends on its death.
The start() method of the Thread class is used to initiate the execution of a thread and it goes
into runnable state and the sleep() and wait() methods of the Thread class sends the thread
into non runnable state.
After non runnable state, thread again comes into runnable state and starts its execution. The
run() method of thread is very much important. After executing the run() method, the
lifecycle of thread is completed.
Thread States in Java
A thread is a path of execution in a program that goes through the following states of a
thread. The five states are as follows:
1. New
2. Runnable
3. Running
4. Blocked (Non-runnable state)
5. Dead
Runnable State
The second phase of a new-born thread is the execution phase. When the start() method is
called on a the new instance of a thread, it enters into a runnable state.
In the runnable state, thread is ready for execution and is waiting for availability of the
processor (CPU time). There are many threads that are ready for execution, they all are
waiting in a queue (line).If all threads have equal priority, a time slot is assigned for each
thread execution on the basis of first-come, first-serve manner by CPU.
Running State
Running means Processor (CPU) has allocated time slot to thread for its execution.
In running state, processor gives its time to the thread for execution and executes its run
method. It is the state where thread performs its actual functions. A thread can come into
running state only from runnable state.
A running thread may give up its control in any one of the following situations and can enter
into the blocked state.
1. When sleep() method is invoked on a thread to sleep for specified time period, the
thread is out of queue during this time period. The thread again reenters into the
runnable state as soon as this time period is elapsed.
2. When a thread is suspended using suspend() method for some time in order to satisfy
some conditions. A suspended thread can be revived by using resume() method.
3. When wait() method is called on a thread to wait for some time. The thread in wait
state can be run again using notify() or notifyAll() method.
Blocked State
A thread is considered to be in the blocked state when it is suspended, sleeping, or waiting for
some time in order to satisfy some condition.
Dead State
A thread dies or moves into dead state automatically when its run() method completes the
execution of statements. That is, a thread is terminated or dead when a thread comes out of
run() method. A thread can also be dead when the stop() method is called.
1. /* Thread 1 */
2. class Thread1 extends Thread
3. {
4.
5. public void run()
6. {
7. System.out.println("Thread 1");
8. System.out.println("i in Thread 1 ");
9. for (int i = 1; i <= 5; i++)
10. {
11. System.out.println("i = " + i);
12. try
13. {
14. Thread.sleep(1000);
15. }
16. catch (InterruptedException e)
17. {
18. e.printStackTrace();
19. }
20. }
21. System.out.println("Thread 1 Completed.");
22. }
23. }
24.
25. /* Thread 2 */
26. class Thread2 extends Thread
27. {
28. public void run()
29. {
30. System.out.println("Thread 2");
31. System.out.println("i in Thread 2 ");
32. for (int i = 1; i <= 5; i++)
33. {
34. System.out.println("i = " + i);
35. }
36. System.out.println("Thread 2 Completed.");
37. }
38. }
39.
40. /* Driver code */
41. public class ThreadDemo
42. {
43. public static void main(String[] args) {
44. // life cycle of Thread
45. // Thread's New State
46. Thread1 t1 = new Thread1();
47. Thread2 t2 = new Thread2();
48. // Both the above threads are in runnable state
49. // Running state of Thread1 and Thread2
50. t1.start();
51. // Move control to another thread
52. t2.yield();
53. // Blocked State Thread1
54. try
55. {
56. t1.sleep(1000);
57. }
58. catch (InterruptedException e)
59. {
60. e.printStackTrace();
61. }
62. t2.start();
63. System.out.println("Main Thread End");
64. }
65. }
Output:
Thread 1
i in Thread 1
i=1
Main Thread End
Thread 2
i in Thread 2
i=1
i=2
i=3
i=4
i=5
Thread 2 Completed.
i=2
i=3
i=4
i=5
Thread 1 Completed.
CREATING THREADS:
Thread Class
The simplest way to create a thread in Java is by extending the Thread class and overriding
its run() method. Thread class provide constructors and methods to create and perform
operations on a thread. Thread class extends Object class and implements Runnable interface.
o Thread()
o Thread(String name)
o Thread(Runnable r)
o Thread(Runnable r, String name)
Thread Creation
1) Creating Thread by Extending Thread Class
thread is running...
Starting a Thread
The start() method of the Thread class is used to start a newly created thread. It performs the
following tasks:
thread is running...
Interrupting a Thread:
If any thread is in sleeping or waiting state (i.e. sleep() or wait() is invoked), calling the
interrupt() method on the thread, breaks out the sleeping or waiting state throwing Interrupted
Exception.
if(Thread.interrupted())
{
System.out.println("After Thread.interrupted() call, JVM reset the interrupted value to: " +
Thread.interrupted());
break;
thread.start();
3. import java.lang.*;
4. class ThreadDemo extends Thread
5. {
6. public void run()
7. {
8. System.out.println("Inside run method");
9. }
10. public static void main(String[] args)
11. {
12. ThreadDemo t1 = new ThreadDemo();
13. ThreadDemo t2 = new ThreadDemo();
14. ThreadDemo t3 = new ThreadDemo();
15. System.out.println("t1 thread priority : "+ t1.getPriority());
16. System.out.println("t2 thread priority : "+ t2.getPriority());
17. System.out.println("t3 thread priority : "+ t3.getPriority());
18. t1.setPriority(2);
19. t2.setPriority(5);
20. t3.setPriority(8);
21. // t3.setPriority(21); will throw IllegalArgumentException
22.
23. System.out.println("t1 thread priority : "+ t1.getPriority());
24. System.out.println("t2 thread priority : "+ t2.getPriority());
25. System.out.println("t3 thread priority : "+ t3.getPriority());
26. System.out.println("Currently Executing Thread :Thread.currentThread()
27. .getName());
28. System.out.println("Main thread priority : "+Thread.currentThread()
29. .getPriority());
30. Thread.currentThread().setPriority(10);
31.
32. System.out.println("Main thread priority :"+Thread.currentThread()
33. .getPriority());
34. }
35. }
Output
t1 thread priority : 5
t2 thread priority : 5
t3 thread priority : 5
t1 thread priority : 2
t2 thread priority : 5
t3 thread priority : 8
Currently Executing Thread : main
Main thread priority : 5
Main thread priority : 10
o wait()
o notify()
o notifyAll()
1) wait() method
The wait() method causes current thread to release the lock and wait until either another
thread invokes the notify() method or the notifyAll() method for this object, or a specified
amount of time has elapsed.
Method Description
public final void wait()throws InterruptedException It waits until object is notified.
2) notify() method
The notify() method wakes up a single thread that is waiting on this object's monitor. If any
threads are waiting on this object, one of them is chosen to be awakened. The choice is
arbitrary and occurs at the discretion of the implementation.
wait() sleep()
The wait() method releases the lock. The sleep() method doesn't release the lock.
It should be notified by notify() or notifyAll() methods After the specified amount of time, sleep is comple
Example Program:
1. class Customer{
2. int amount=10000;
3.
4. synchronized void withdraw(int amount)
5. {
6. System.out.println("going to withdraw...");
7.
8. if(this.amount<amount)
9. {
10. System.out.println("Less balance; waiting for deposit...");
11. try
12. {
13. wait();
14. }
15. catch(Exception e)
16. { }
17. }
18. this.amount-=amount;
19. System.out.println("withdraw completed...");
20. }
21.
22. synchronized void deposit(int amount){
23. System.out.println("going to deposit...");
24. this.amount+=amount;
25. System.out.println("deposit completed... ");
26. notify();
27. }
28. }
29.
30. class Test{
31. public static void main(String args[]){
32. final Customer c=new Customer();
33. new Thread(){
34. public void run(){c.withdraw(15000);}
35. }c.start();
36. new Thread(){
37. public void run(){c.deposit(10000);}
38. }c.start();
39.
40. }}
Output:
going to withdraw...
Less balance; waiting for deposit...
going to deposit...
deposit completed...
withdraw completed
Syntax
synchronized(objectidentifier) {
// Access shared variables and other shared resources
}
the objectidentifier is a reference to an object whose lock associates with the monitor that
the synchronized statement represents.
Understanding Threads and Shared Resources
Thread represents an independent path of execution within a program. When multiple threads
access shared resources concurrently, problems may arise due to unpredictable interleaving of
operations. Consider a scenario where two threads increment a shared variable concurrently:
1. class Counter {
2. private int count = 0;
3. public void increment() {
4. count++;
5. }
6. }
If two threads execute increment() simultaneously, they might read the current value of
count, increment it, and write it back concurrently. This can result in lost updates or incorrect
final values due to race conditions.
Introducing Synchronization
Synchronization in Java tackles these problems through the capacity of a single thread to
have exclusive access to either a synchronized block of code or a synchronized method
associated with an object in question at a time. There are two primary mechanisms for
synchronization in Java: synchronized blocks and synchronized methods.
Synchronized Blocks
Synchronized block provides exclusive access to shared resources, and only one thread is
allowed to execute it in the same time frame. It's structured as follows:
1. synchronized (object) {
2. // Synchronized code block
3. }
This monitor object or lock is the subject. While only one thread can be holding a lock on a
monitor object at one instance. Other threads that want to go into the synchronized blocks
with this object must wait till the lock becomes available.
Synchronized Methods
In Java, you can declare entire methods as synchronized which prevent multiple threads from
accessing the method simultaneously. With this, synchronization becomes a simpler process
because the mechanism is applied to all invocations of the synchronized method
automatically.
1. class SynchronizedCounter {
2. private int count = 0;
3. public synchronized void increment() {
4. count++;
5. }
6. public synchronized int getCount() {
7. return count;
8. }
9. }
With this modification, concurrent calls to increment() or getCount() will be synchronized,
preventing race conditions.
TestSynchronization1.java
1. class Table {
2. // Method to print the table, not synchronized
3. void printTable(int n) {
4. for(int i = 1; i <= 5; i++) {
5. // Print the multiplication result
6. System.out.println(n * i);
7. try {
8. // Pause execution for 400 milliseconds
9. Thread.sleep(400);
10. } catch(Exception e) {
11. // Handle any exceptions
12. System.out.println(e);
13. }
14. }
15. }
16. }
17. class MyThread1 extends Thread {
18. Table t;
19. // Constructor to initialize Table object
20. MyThread1(Table t) {
21. this.t = t;
22. }
23. // Run method to execute thread
24. public void run() {
25. // Call printTable method with argument 5
26. t.printTable(5);
27. }
28. }
29. class MyThread2 extends Thread {
30. Table t;
31. // Constructor to initialize Table object
32. MyThread2(Table t) {
33. this.t = t;
34. }
35. // Run method to execute thread
36. public void run() {
37. // Call printTable method with argument 100
38. t.printTable(100);
39. }
40. }
41. class TestSynchronization1 {
42. public static void main(String args[]) {
43. // Create a Table object
44. Table obj = new Table();
45. // Create MyThread1 and MyThread2 objects with the same Table object
46. MyThread1 t1 = new MyThread1(obj);
47. MyThread2 t2 = new MyThread2(obj);
48. // Start both threads
49. t1.start();
50. t2.start();
51. }
52. }
Output:
5
100
10
200
15
300
20
400
25
500
TESTSYNCHRONIZATION2.JAVA
1. class Table {
2. // Synchronized method to print the table
3. synchronized void printTable(int n) {
4. for(int i = 1; i <= 5; i++) {
5. // Print the multiplication result
6. System.out.println(n * i);
7. try {
8. // Pause execution for 400 milliseconds
9. Thread.sleep(400);
10. } catch(Exception e) {
11. // Handle any exceptions
12. System.out.println(e);
13. }
14. }
15. }
16. }
17. class MyThread1 extends Thread {
18. Table t;
19. // Constructor to initialize Table object
20. MyThread1(Table t) {
21. this.t = t;
22. }
23. // Run method to execute thread
24. public void run() {
25. // Call synchronized method printTable with argument 5
26. t.printTable(5);
27. }
28. }
29. class MyThread2 extends Thread {
30. Table t;
31. // Constructor to initialize Table object
32. MyThread2(Table t) {
33. this.t = t;
34. }
35. // Run method to execute thread
36. public void run() {
37. // Call synchronized method printTable with argument 100
38. t.printTable(100);
39. }
40. }
41. public class TestSynchronization2 {
42. public static void main(String args[]) {
43. // Create a Table object
44. Table obj = new Table();
45. // Create MyThread1 and MyThread2 objects with the same Table object
46. MyThread1 t1 = new MyThread1(obj);
47. MyThread2 t2 = new MyThread2(obj);
48. // Start both threads
49. t1.start();
50. t2.start();
51. }
52. }
Output:
5
10
15
20
25
100
200
300
400
500
Consistency: By using synchronization, you can ensure that concurrent operations on shared
resources are performed in a consistent and predictable manner. This is crucial for
maintaining the correctness of the program's logic and preventing unexpected outcomes.
Data Visibility: Synchronization mechanisms such as locks and memory barriers guarantee
that changes made by one thread to shared variables are visible to other threads. This ensures
that threads always see the most up-to-date values of shared data, preventing inconsistencies
due to stale data.
Potential for Deadlocks: Incorrect use of synchronization primitives can lead to deadlocks,
where threads are blocked indefinitely, waiting for each other to release locks. Deadlocks are
challenging to debug and can cause the entire application to hang, impacting its availability
and reliability.
Complexity and Maintenance: Synchronized code can be more complex and error-prone
than single-threaded or lock-free alternatives. Managing locks, ensuring proper lock
acquisition and release, and avoiding deadlocks require careful design and testing, increasing
the complexity and maintenance burden of the codebase.
Potential for Livelocks: Livelocks are similar to deadlocks but occur when threads
continuously change their states in response to each other, preventing any of them from
making progress. Livelocks can occur when threads repeatedly acquire and release locks in a
specific pattern without making progress toward resolving the contention.
Potential for Performance Degradation with I/O Operations: Synchronization may lead
to performance degradation when threads block on I/O operations while holding locks. This
can cause other threads waiting for the same locks to be blocked unnecessarily, reducing
overall throughput and responsiveness.
Producer-Consumer solution using threads in Java
The problem describes two processes, the producer and the consumer, which share a
common, fixed-size buffer used as a queue.
The producer’s job is to generate data, put it into the buffer, and start again.
At the same time, the consumer is consuming the data (i.e. removing it from the buffer),
one piece at a time.
Both producer and consumer may try to update the queue at the same time. This could
lead to data loss or inconsistencies.
Producers might be slower than consumers. In such cases, the consumer would
process elements fast and wait.
In some cases, the consumer can be slower than the producer. This situation leads to a
queue overflow issue.
In real scenarios, we may have multiple producers, multiple consumers, or both. This
may cause the same message to be processed by different consumers.
The diagram below depicts a case with multiple producers and multiple consumers:
In Java, the synchronized block uses an object to achieve thread synchronization. Each
object has an intrinsic lock. Only the thread that acquires the lock first is allowed to execute
the synchronized block.