0% found this document useful (0 votes)
3 views32 pages

java unit-3

The document provides a comprehensive overview of exception handling in Java, explaining what exceptions are, the types of exceptions (checked, unchecked, and errors), and the advantages of using exception handling to maintain application flow. It details the keywords used in exception handling, such as try, catch, finally, throw, and throws, along with examples of common exceptions and their handling. Additionally, the document touches on multithreading and multiprocessing in Java, highlighting their benefits and drawbacks.

Uploaded by

rsgnr2006
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
Download as docx, pdf, or txt
0% found this document useful (0 votes)
3 views32 pages

java unit-3

The document provides a comprehensive overview of exception handling in Java, explaining what exceptions are, the types of exceptions (checked, unchecked, and errors), and the advantages of using exception handling to maintain application flow. It details the keywords used in exception handling, such as try, catch, finally, throw, and throws, along with examples of common exceptions and their handling. Additionally, the document touches on multithreading and multiprocessing in Java, highlighting their benefits and drawbacks.

Uploaded by

rsgnr2006
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
Download as docx, pdf, or txt
Download as docx, pdf, or txt
You are on page 1/ 32

Exception Handling in Java

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.

What is Exception in Java?


Dictionary Meaning: Exception is an abnormal condition.

In Java, an exception is an event that disrupts the normal flow of the program. It is an object
which is thrown at runtime.

What is Exception Handling?


Exception Handling is a mechanism to handle runtime errors such as
ClassNotFoundException, IOException, SQLException, RemoteException, etc.

Advantage of Exception Handling


The core advantage of exception handling is to maintain the normal flow of the
application. An exception normally disrupts the normal flow of the application; that is why
we need to handle exceptions. Let's consider a scenario:

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.

Hierarchy of Java Exception classes


The java.lang.Throwable class is the root class of Java Exception hierarchy inherited by two
subclasses: Exception and Error. The hierarchy of Java Exception classes is given below:
Types of Java Exceptions
There are mainly two types of exceptions: checked and unchecked. An error is considered as
the unchecked exception. However, according to Oracle, there are three types of exceptions
namely:

1. Checked Exception
2. Unchecked Exception
3. Error

Difference between Checked and Unchecked Exceptions

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:

IO Exception: An exception is thrown when an input/output operation fails, such as when


reading from or writing to a file.

SQL Exception: It is thrown when an error occurs while accessing a database.

ClassNotFoundException: It is thrown when an application tries to load a class through its


string name using methods like Class.forName(), but the class with the specified name cannot
be found in the class path.

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.

Recovery Possible: Checked exceptions typically represent recoverable conditions, such as


file not found or database connection failure, where the application may take corrective
action.

Unchecked Exceptions (Runtime Exceptions)


Unchecked exceptions, also known as runtime exceptions, are not checked at compile-time.
These exceptions usually occur due to programming errors, such as logic errors or incorrect
assumptions in the code. They do not need to be declared in the method signature using the
throws keyword, making it optional to handle them. Examples of unchecked exceptions
include:

NullPointerException: It is thrown when trying to access or call a method on an object


reference that is null.

ArrayIndexOutOfBoundsException: It occurs when we try to access an array element with


an invalid index.

ArithmeticException: It is thrown when an arithmetic operation fails, such as division by


zero.

Runtime Errors: Unchecked exceptions often represent programming errors or unexpected


conditions during runtime, such as null references or array index out of bounds.

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.

Examples: Examples of errors include OutOfMemoryError, StackOverflowError,


NoClassDefFoundError, etc.

Critical Conditions: Errors usually indicate critical conditions, such as JVM failures or
system resource exhaustion, where the application cannot recover.

Java Exception Keywords


Keyword Description

The "try" keyword is used to specify a block where we


should place an exception code. It means we can't use
Try
try block alone. The try block must be followed by
either catch or finally.

The "catch" block is used to handle the exception. It


must be preceded by try block which means we can't
Catch
use catch block alone. It can be followed by finally
block later.

The "finally" block is used to execute the necessary


Finally code of the program. It is executed whether an
exception is handled or not.

Throw The "throw" keyword is used to throw an exception.

The "throws" keyword is used to declare exceptions. It


specifies that there may occur an exception in the
Throws
method. It doesn't throw an exception. It is always
used with method signature.

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:

1) A scenario where ArithmeticException occurs


If we divide any number by zero, there occurs an ArithmeticException.

int a=50/0;//ArithmeticException

2) A scenario where NullPointerException occurs


If we have a null value in any variable, performing any operation on the variable throws a
NullPointerException.

String s=null;
System.out.println(s.length());//NullPointerException

3) A scenario where NumberFormatException occurs


If the formatting of any variable or number is mismatched, it may result into
NumberFormatException. Suppose we have a string variable that has characters; converting
this variable into digit will cause NumberFormatException.

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.

int a[]=new int[5];


1. a[10]=50; //ArrayIndexOutOfBoundsException

Java try-catch block

Java try block


Java try block is used to enclose the code that might throw an exception. It must be used
within the method.

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.

Java try block must be followed by either catch or finally block.

Syntax of Java try-catch


try{
//code that may throw an exception
}
catch(Exception_class_Name ref)
{
}
Syntax of try-finally block
try{
//code that may throw an exception
}
Finally
{
}

Example 1
TryCatchExample1.java

1. public class TryCatchExample1


2. {
3. public static void main(String[] args)
4. {
5. int data=50/0; //may throw exception
6.
7. System.out.println("rest of the code");
8. }
9.
10. }

Output:

Exception in thread "main" java.lang.ArithmeticException: / by zero

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.

Solution by exception handling


Let's see the solution of the above problem by a java try-catch block.

Example 2
TryCatchExample2.java

1. public class TryCatchExample2 {


2.
3. public static void main(String[] args) {
4. try
5. {
6. int data=50/0; //may throw exception
7. }
8. //handling the exception
9. catch(ArithmeticException e)
10. {
11. System.out.println(e);
12. }
13. System.out.println("rest of the code");
14. }
15.
16. }

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.

Handling Multiple Exceptions:


You can handle multiple types of exceptions by providing multiple catch blocks, each
catching a different type of exception. This allows you to tailor your exception handling logic
based on the specific type of exception thrown. Here's an example:

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. }

File Name: ArithmeticExceptionExample.java

1. public class ArithmeticExceptionExample


2. {
3. public static void main(String[] args)
4. {
5. int dividend = 10;
6. int divisor = 0;
7. try
8. {
9. int result = dividend / divisor; // Division by zero
10. System.out.println("Result: " + result);
11. }
12. catch (ArithmeticException e)
13. {
14. System.out.println("Error: Division by zero is not allowed.");
15. }
16. }
17. }
Output:

Error: Division by zero is not allowed.

File Name: NullPointerExceptionExample.java

1. public class NullPointerExceptionExample


2. {
3. public static void main(String[] args)
4. {
5. String str = null;
6. try
7. {
8. int length = str.length();
9. System.out.println("Length of the string: " + length);
10. }
11. catch (NullPointerException e)
12. {
13. System.out.println("Error: Null reference encountered.");
14.
15. }
16. }
17. }
Output:

Error: Null reference encountered.

NumberFormatExceptionExample.java

1. public class NumberFormatExceptionExample


2. {
3. public static void main(String[] args)
4. {
5. String str = "abc";
6. try
7. {
8. int num = Integer.parseInt(str);
9. System.out.println("Parsed number: " + num);
10. }
11. catch (NumberFormatException e)
12. {
13. System.out.println("Error: Unable to parse the string as an integer.");
14. }
15. }
16. }
Output:

Error: Unable to parse the string as an integer.

ArrayIndexOutOfBoundsExceptionExample.java

1. public class ArrayIndexOutOfBoundsExceptionExample


2. {
3. public static void main(String args[])
4. {
5. int numbers[] = {1, 2, 3, 4, 5};
6. try
7. {
8. int index = 10;
9. int value = numbers[index];
10. System.out.println("Value at index " + index + ": " + value);
11. }
12. catch (ArrayIndexOutOfBoundsException e)
13. {
14. System.out.println("Error: Index is out of bounds.");
15. }
16. } `
17. }
Output:

Error: Index is out of bounds.


RETHROWING AN EXCEPTION:

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:

Multiprocessing and multithreading:


Multithreading in Java
The process of dividing a program into several threads so that they may do tasks
simultaneously is known as multithreading. Creating and maintaining thread objects in Java
is usually accomplished by utilizing the classes found in the Java.lang package. Implementing
the Java.lang or using the Thread package.executable interface.

Benefits of Multiple Threading:

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

1. Complex Synchronisation: It might be difficult and error-prone to handle


synchronization and avoid race situations.
2. Lack of Isolation: Since threads share memory, a single bad thread may have an
impact on the entire program.
3. Limited CPU Utilisation: When multitasking on CPU-bound activities, it's possible
that many CPU cores won't be completely used.

Multithreading.java

1. class MyThread extends Thread


2. {
3. public void run()
4. {
5. for (int j = 1; j <= 7; j++)
6. {
7. System.out.println("Thread " + Thread.currentThread().getId() + " - Count: " + j);
8. }
9. }
10. }
11. public class Multithreading
12. {
13. public static void main(String[] args)
14. {
15. MyThread t1 = new MyThread();
16. MyThread t2 = new MyThread();
17. t1.start();
18. t2.start();
19. }
20. }
Output:

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.

Benefits of Multiple Processing

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

1. Greater Resource Overhead: Compared to threads, processes require more system


resources to create and maintain.
2. Slower Context Switching: Compared to threads, context switching between
processes happens more slowly, which might be wasteful for operations involving
input/output.
3. Communication Overhead: For processes to interact, inter-process communication
(IPC) protocols are needed, and these might be more complicated.
Aspect Multithreading Multiprocessing

Threads efficiently share data by Processes isolate data by having


Resource Sharing
sharing memory space. their memory region.

quicker context changes while slower transitions between


Context Switching
working on the same task. different processes' contexts.

Shared memory space causes Easier synchronization because


Synchronization
complex synchronization. each process has its memory.

Perhaps best suited for I/O-bound Effectively employs several CPU


CPU Utilization workloads, numerous CPU cores cores, appropriate for jobs that are
may not be fully utilized. CPU-intensive.

Absence of memory isolation: An


High memory isolation, means that
application as a whole may be
Isolation if one process crashes, it won't
impacted by a single maladroit
affect the others.
thread.

Less memory use and resource Higher memory use and resource
Overhead
overhead. overhead.

Shared memory, complicated Mechanisms for interprocess


Communication synchronization, and direct method communication (IPC) are
calls. necessary.

Thread States in Java

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

New (Newborn State)


When an instance of the Thread class is created a new thread is born and is known to be in
New-born state

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.

Java Thread Program


ThreadDemo.java

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:

Multithreading is a fundamental concept in Java programming, allowing developers to


execute multiple tasks concurrently within a single program. Threads are lightweight
processes that run within the context of a larger process

There are the following two ways to create a thread:

o By Extending Thread Class


o By Implementing Runnable Interface

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.

Constructors of Thread Class

o Thread()
o Thread(String name)
o Thread(Runnable r)
o Thread(Runnable r, String name)

Thread Class Methods

1. public void run(): is used to perform action for a thread.


2. public void start(): starts the execution of the thread.JVM calls the run() method on
the thread.
3. public void sleep(long miliseconds): Causes the currently executing thread to sleep
(temporarily cease execution) for the specified number of milliseconds.
4. public void join(): waits for a thread to die.
5. public void join(long miliseconds): waits for a thread to die for the specified
miliseconds.
6. public int getPriority(): returns the priority of the thread.
7. public int setPriority(int priority): changes the priority of the thread.
8. public String getName(): returns the name of the thread.
9. public void setName(String name): changes the name of the thread.
10. public Thread currentThread(): returns the reference of currently executing thread.

Thread Creation
1) Creating Thread by Extending Thread Class

File Name: Multi.java

1. class Multi extends Thread{


2. public void run(){
3. System.out.println("thread is running...");
4. }
5. public static void main(String args[]){
6. Multi t1=new Multi();
7. t1.start();
8. }
9. }
Output:

thread is running...

By Implementing Runnable Interface


Another approach to creating threads in Java is by implementing the Runnable interface. The
Runnable interface should be implemented by any class whose instances are intended to be
executed by a thread. Runnable interface have only one method named run(). This approach
is preferred when we want to separate the task from the thread itself, promoting better
encapsulation and flexibility.

public void run(): is used to perform action for a thread.

Starting a Thread
The start() method of the Thread class is used to start a newly created thread. It performs the
following tasks:

o A new thread starts (with new callstack).


o The thread moves from New state to the Runnable state.
o When the thread gets a chance to execute, its target run() method will run.
2) Java Thread Example by implementing Runnable interface
FileName: Multi3.java

1. class Multi3 implements Runnable{


2. public void run(){
3. System.out.println("thread is running...");
4. }
5.
6. public static void main(String args[]){
7. Multi3 m1=new Multi3();
8. Thread t1 =new Thread(m1); // Using the constructor Thread(Runnable r)
9. t1.start();
10. }
11. }
Output:

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.

Methods for Interrupting a Thread


The Thread class provides three methods for interrupting a thread -
 void interrupt() − Interrupts the thread.
 static boolean interrupted() − Tests whether the current thread has been interrupted.
 boolean isInterrupted() − Tests whether the thread has been interrupted.

class Task implements Runnable

public void run()

for (int i = 0; i < 5; i++)

System.out.println("[" + Thread.currentThread().getName() + "] Message " + i);

if(Thread.interrupted())
{

System.out.println("This thread was interruped by someone calling this Thread.interrupt()");

System.out.println("Cancelling task running in thread " +


Thread.currentThread().getName());

System.out.println("After Thread.interrupted() call, JVM reset the interrupted value to: " +
Thread.interrupted());

break;

public class TestThread

public static void main(String[] args)

System.out.println("Thread main started");

final Task task = new Task();

final Thread thread = new Thread(task);

thread.start();

thread.interrupt(); // calling interrupt() method

System.out.println("Main Thread finished");

Thread priorities in multithreading:


Whenever we create a thread in Java, it always has some priority assigned to it. Priority can
either be given by JVM while creating the thread or it can be given by the programmer
explicitly.
Priority here which is represented by numbers ranging from 1 to 10.
 The default priority is set to 5 as excepted.
 Minimum priority is set to 1.
 Maximum priority is set to 10.
Here 3 constants are defined in it namely as follows:
1. public static int NORM_PRIORITY
2. public static int MIN_PRIORITY
3. public static int MAX_PRIORITY

how to get and set priority of a thread in java.


1. public final int getPriority(): java.lang.Thread.getPriority() method returns priority of
given thread.
2. public final void setPriority(int newPriority): java.lang.Thread.setPriority() method
changes the priority of thread to the value newPriority.

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

Inter-thread Communication in Java


Inter-thread communication or Co-operation is all about allowing synchronized threads to
communicate with each other.

Cooperation (Inter-thread communication) is a mechanism in which a thread is paused


running in its critical section and another thread is allowed to enter (or lock) in the same
critical section to be executed.It is implemented by following methods of Object class:

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.

public final void wait(long timeout)throws


It waits for the specified amount of time.
InterruptedException

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.

1. Threads enter to acquire lock.


2. Lock is acquired by on thread.
3. Now thread goes to waiting state if you call wait() method on the object. Otherwise it
releases the lock and exits.
4. If you call notify() or notifyAll() method, thread moves to the notified state (runnable
state).
5. Now thread is available to acquire lock.
6. After completion of the task, thread releases the lock and exits the monitor state of the
object.

Difference between wait and sleep?


Let's see the important differences between wait and sleep methods.

wait() sleep()

The wait() method releases the lock. The sleep() method doesn't release the lock.

It is a method of Object class It is a method of Thread class


It is the non-static method It is the static method

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

THREAD SYNCHRONIZATION IN JAVA


Java programming language provides a very handy way of creating threads and
synchronizing their task by using synchronized blocks. You keep shared resources within
this block.

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.

Example: Synchronized Counter

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.

Understanding The Problem Without Synchronization


In this example, there is no synchronization, so output is inconsistent. Let's see the example:

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

Advantages of Synchronization in Java


Thread Safety: Synchronization ensures that shared resources are accessed by only one
thread at a time, preventing race conditions and maintaining data integrity. This makes it
easier to write multi-threaded programs without worrying about unpredictable behaviours
caused by concurrent access.

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.

Prevention of Deadlocks: Synchronization provides tools to prevent deadlocks, a situation


where two or more threads are blocked indefinitely, waiting for each other to release
resources. By following best practices such as acquiring locks in a consistent order and using
timeouts, you can minimize the risk of deadlocks in your multi-threaded applications.

Coordination: Synchronization facilitates coordination and communication between threads


by allowing them to wait for certain conditions to be met before proceeding. This enables the
implementation of synchronization primitives such as semaphores, mutexes, and barriers,
which are essential for designing complex multi-threaded algorithms.

Efficient Resource Utilization: While synchronization adds overhead to multi-threaded


programs due to locking and context switching, it enables efficient utilization of shared
resources such as CPU time, memory, and I/O devices. By allowing multiple threads to work
cooperatively without interfering with each other, synchronization maximizes the throughput
and responsiveness of the application.

Compatibility with Legacy Code: Synchronization is a fundamental concept in Java


concurrency that has been widely adopted in libraries, frameworks, and existing codebases.
By leveraging synchronization, developers can ensure compatibility with legacy code and
libraries that rely on thread-safe programming practices.

Disdvantages of Synchronization in Java


Performance Overhead: Synchronization involves acquiring and releasing locks, which
introduces overhead due to context switching and contention for shared resources. This can
degrade performance, especially in highly concurrent applications where many threads
contend for the same locks.

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.

Reduced Scalability: Synchronization can limit the scalability of multi-threaded applications


by introducing bottlenecks. When multiple threads contend for the same locks, they may
spend significant time waiting, reducing overall throughput and scalability, particularly on
systems with many CPU cores.

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.

Difficulty in Debugging: Synchronization-related issues such as race conditions, deadlocks,


and livelocks can be challenging to debug, especially in complex multi-threaded applications.
These issues may occur sporadically and may not be reproducible in controlled environments,
making them hard to diagnose and fix.

Decreased Concurrency: Overuse of synchronization can lead to decreased concurrency, as


threads may spend more time waiting for locks than performing useful work. Fine-grained
locking can mitigate this issue but increases complexity and may introduce additional
overhead.

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:

We need to handle resource sharing and synchronization to solve a few complexities:

 Synchronization on queue while adding and removing data


 When the queue is empty, the consumer has to wait until the producer adds new data
to the queue
 When the queue is full, the producer has to wait until the consumer consumes data
and the queue has some empty buffer

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.

You might also like