0% found this document useful (0 votes)
2 views

unit 2 oop

The document provides an overview of Exception Handling and Multithreading in Java, detailing the mechanisms to manage runtime errors and the lifecycle of threads. It explains the types of exceptions, the keywords used for exception handling (try, catch, throw, throws, finally), and the differences between checked and unchecked exceptions. Additionally, it outlines the advantages of multithreading, the states of a thread, and methods for creating threads.

Uploaded by

prabhatap4786
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views

unit 2 oop

The document provides an overview of Exception Handling and Multithreading in Java, detailing the mechanisms to manage runtime errors and the lifecycle of threads. It explains the types of exceptions, the keywords used for exception handling (try, catch, throw, throws, finally), and the differences between checked and unchecked exceptions. Additionally, it outlines the advantages of multithreading, the states of a thread, and methods for creating threads.

Uploaded by

prabhatap4786
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 58

OOPS NOTES: UNIT-2

Exception Handling
The exception handling in java is one of the powerful mechanism to handle the runtime
errors so that normal flow of the application can be maintained.
Java provides us facility to create our own exceptions which are basically derived classes of
Exception.
If an exception occurs, which has not been handled by programmer then program execution gets
terminated and a system generated error message is shown to the user. For example look at the
system generated exception below: An exception generated by the system is given below

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


ExceptionDemo.main(ExceptionDemo.java:5) ExceptionDemo : The class name main : The method
name ExceptionDemo.java : The filename java:5 : Line number
Java exception handling is managed via five keywords: try, catch, throw, throws, and finally

Advantage of Exception Handling


The core advantage of exception handling is to maintain the normal flow of the application.
Exception normally disrupts the normal flow of the application that is why we use exception
handling.

What is an exception?
An Exception is an unwanted event that interrupts the normal flow of the program. When an
exception occurs program execution gets terminated. In such cases we get a system generated error
message. The good thing about exceptions is that they can be handled in Java. By handling the
exceptions we can provide a meaningful message to the user about the issue rather than a system
generated message, which may not be understandable to a user.
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:
Difference between error and exception
Errors indicate that something severe enough has gone wrong, the application should crash rather
than try to handle the error. Exceptions are events that occurs in the code. A programmer can handle
such conditions and take necessary corrective actions.
Few examples: NullPointerException – When you try to use a reference that points to null.
ArithmeticException – When bad data is provided by user, for example, when you try to divide a
number by zero this exception occurs because dividing a number by zero is undefined.
ArrayIndexOutOfBoundsException – When you try to access the elements of an array out of its
bounds, for example array size is 5 (which means it has five elements) and you are trying to access
the 10th element.
Types of Exception There are mainly two types of exceptions: checked and unchecked where error
is considered as unchecked exception. The sun microsystem says there are three types of
exceptions:
1. Checked Exception
2. Unchecked Exception
3. Error

1. Checked Exception
All exceptions other than Runtime Exceptions are known as Checked exceptions as the compiler
checks them during compilation to see whether the programmer has handled them or not. If these
exceptions are not handled/declared in the program, you will get compilation error.
The classes that extend Throwable class except RuntimeException and Error
For example, SQLException, IOException, ClassNotFoundException etc.
2. Unchecked Exception
Runtime Exceptions are also known as Unchecked Exceptions. These exceptions are not checked at
compile-time so compiler does not check whether the programmer has handled them or not but it’s
the responsibility of the programmer to handle these exceptions and provide a safe exit.
The classes that extend RuntimeException
For example, ArithmeticException, NullPointerException, ArrayIndexOutOfBoundsException etc.
4. Error
Error is irrecoverable e.g. OutOfMemoryError, VirtualMachineError, AssertionError etc

Java Exception Keywords

try:
The "try" keyword is used to specify a block where we should place an exception code. It means we
can't use try block alone. The try block must be followed by either catch or finally.

catch
The "catch" block is used to handle the exception. It must be preceded by try block which means we
can't use catch block alone. It can be followed by finally block later.
finally
The "finally" block is used to execute the necessary code of the program. It is executed whether an
exception is handled or not.

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

throws
The "throws" keyword is used to declare exceptions. It specifies that there may occur an exception
in the method. It doesn't throw an exception. It is always used with method signature.

Java Exception Handling Example

public class ExceptionExample{


public static void main(String args[]){
try{
//code that may raise exception
int data=100/0;
}catch(ArithmeticException e){System.out.println(e);}
//rest code of the program
System.out.println("rest of the code...");
}
}

Output:
Exception in thread main java.lang.ArithmeticException:/ by zero
rest of the code...
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.

1. String s=null;
1. 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.

1. String s="abc";
2. 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.

1. int a[]=new int[5];


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

Java Multi-catch block

A try block can be followed by one or more catch blocks. Each catch block must contain a different
exception handler. So, if you have to perform different tasks at the occurrence of different
exceptions, use java multi-catch block.

At a time only one exception occurs and at a time only one catch block is executed.

public class MultipleCatchBlock1 {

public static void main(String[] args) {

try{
int a[]=new int[5];
a[5]=30/0;
}
catch(ArithmeticException e)
{
System.out.println("Arithmetic Exception occurs");
}
catch(ArrayIndexOutOfBoundsException e)
{
System.out.println("ArrayIndexOutOfBounds Exception occurs");
}
catch(Exception e)
{
System.out.println("Parent Exception occurs");
}
System.out.println("rest of the code");
}
}

Output
Arithmetic Exception occurs
rest of the code

public class MultipleCatchBlock2 {

public static void main(String[] args) {

try{
int a[]=new int[5];

System.out.println(a[10]);
}
catch(ArithmeticException e)
{
System.out.println("Arithmetic Exception occurs");
}
catch(ArrayIndexOutOfBoundsException e)
{
System.out.println("ArrayIndexOutOfBounds Exception occurs");
}
catch(Exception e)
{
System.out.println("Parent Exception occurs");
}
System.out.println("rest of the code");
}
}
Output
ArrayIndexOutOfBounds Exception occurs
rest of the code
Java throw
The throw keyword in Java is used to explicitly throw an exception from a method or any block of
code. We can throw either checked or unchecked exception. The throw keyword is mainly used to
throw custom exceptions.

Syntax in Java

throw new exception_class("error message");

Example:
throw new ArithmeticException("/ by zero");
Example

public class TestThrow1 {


//function to check if person is eligible to vote or not
public static void validate(int age) {
if(age<18) {
//throw Arithmetic exception if not eligible to vote
throw new ArithmeticException("Person is not eligible to vote");
}
else {
System.out.println("Person is eligible to vote!!");
}
}
//main method
public static void main(String args[]){
//calling the function
validate(13);
System.out.println("rest of the code...");
}

3. }
Output:
Java throws
throws is a keyword in Java that is used in the signature of a method to indicate that this method
might throw one of the listed type exceptions. The caller to these methods has to handle the
exception using a try-catch block.

Syntax of Java throws

type method_name(parameters) throws exception_list

exception_list is a comma separated list of all the


exceptions which a method might throw.

public class TestThrows {


//defining a method
public static int divideNum(int m, int n) throws ArithmeticException {
int div = m / n;
return div;
}
//main method
public static void main(String[] args) {
TestThrows obj = new TestThrows();
try {
System.out.println(obj.divideNum(45, 0));
}
catch (ArithmeticException e){
System.out.println("\nNumber cannot be divided by 0");
}

System.out.println("Rest of the code..");


}
}
Java throw and throws Example

ublic class TestThrowAndThrows


{
// defining a user-defined method
// which throws ArithmeticException
static void method() throws ArithmeticException
{
System.out.println("Inside the method()");
throw new ArithmeticException("throwing ArithmeticException");
}
//main method
public static void main(String args[])
{
try
{
method();
}
catch(ArithmeticException e)
{
System.out.println("caught in main() method");
}
}
}
User-defined Exception

class UserDefinedException extends Exception


{
public UserDefinedException(String str)
{
// Calling constructor of parent Exception
super(str);
}
}
// Class that uses above MyException
public class TestThrow3
{
public static void main(String args[])
{
try
{
// throw an object of user defined exception
throw new UserDefinedException("This is user-defined exception");
}
catch (UserDefinedException ude)
{
System.out.println("Caught the exception");
// Print the message from MyException object
System.out.println(ude.getMessage());
}
}
}

To prevent this compile time error we can handle the exception in two ways:
1. By using try catch
2. By using the throws keyword

Difference between throw and throws

S.No Throw Throws

1 The throw keyword is used inside a The throws keyword is used in the function
function. signature.

2 The throw keyword is used to throw an The throws keyword can be used to declare
exception explicitly. It can throw only one multiple exceptions, separated by a comma.
exception at a time Whichever exception occurs, if matched with
the declared ones, is thrown automatically
then.

3 An instance of exception to be thrown. Class names of exceptions to be thrown.

4 It is only used to propagate the It is used to propagate the checked


unchecked Exceptions Exceptions
Multithreading in Java

Multithreading in Java is a process of executing multiple threads simultaneously.

A thread is a lightweight sub-process, the smallest unit of processing.

Multiprocessing and multithreading, both are used to achieve multitasking.

However, we use multithreading than multiprocessing because threads use a shared memory area.
They don't allocate separate memory area so saves memory, and context-switching between the
threads takes less time than process.

Java Multithreading is mostly used in games, animation, etc.

Advantages of Java Multithreading

1) It doesn't block the user because threads are independent and you can perform multiple
operations at the same time.

2) You can perform many operations together, so it saves time.

3) Threads are independent, so it doesn't affect other threads if an exception occurs in a single
thread.

Multitasking

Multitasking is a process of executing multiple tasks simultaneously. We use multitasking to utilize


the CPU.

Multitasking can be achieved in two ways:

1. Process-based Multitasking (Multiprocessing)

2. Thread-based Multitasking (Multithreading)

1. Process-based Multitasking (Multiprocessing)

 Each process has an address in memory. In other words, each process

 allocates a separate memory area.

 A process is heavyweight.
 Cost of communication between the process is high.

 Switching from one process to another requires some time for saving and loading registers,
memory maps, updating lists, etc.

2. Thread-based Multitasking (Multithreading)

 A thread is lightweight.

 Threads share the same address space.

 Context-switching between threads is normally inexpensive.

 Communication between threads is normally inexpensive

What is Thread in java

 A thread is a lightweight subprocess, the smallest unit of processing.

 It is a separate path of execution.

 Threads are independent.

 If there occurs exception in one thread, it doesn't affect other threads.

 It uses a shared memory area.

 It has a single flow of control. It has a beginning, a body, and an end, and executes
commands sequentially

LIFE CYCLE OF A THREAD

During the lifetime of a thread, it can enter many states. It includes:

1. Newborn state

2. Runnable state

3. Running state

4. Blocked state
5. Dead state

Newborn State

When we create a thread object, the thread is born and is said to be in newborn state. At this state,
we can do only one of the following things with it:

1. We can schedule it for running using start() method.

2. We can kill it using stop() method.

If scheduled, it moves to the runnable state.

Runnable State
The runnable state means that the thread is ready for execution and is waiting for the availability of
the processor. However, if we want a thread to relinquish control to another thread of equal priority
before its turn comes, we can do so by using the yield() method.

Running State

Running means that the processor has given its time to the thread for its execution. The thread runs
until it relinquishes control on its own or it is pre-empted by a higher priority thread.

A running thread may relinquish its control in one of the following situations.

1) It has been suspended by using suspend() method. A suspended thread can be revived by using
the resume() method. This approach is useful when we do not want to kill a thread but want to
suspend it for some time due to certain reason.

2) It has been made to sleep. We can make a thread to sleep for a specified time period using the
method sleep (time) where time is in milliseconds.

It means that the thread is out of the queue during this time period. As soon as this time period is
elapsed, the thread re-enters the runnable state.
3) It has been told to wait until some event occurs. It is done using the wait() method.

The thread can be scheduled to run again using the notify() method.

Blocked State

A thread is said to be in blocked state when it is prevented from entering the runnable state and
subsequently the running state.

It happens when the thread is suspended, sleeping or waiting in order to satisfy certain
requirements.

A blocked thread is considered “not runnable” but it is not dead and therefore fully qualified to run
again.

Dead State

Every thread has a life cycle. A running thread ends its life when it completes executing its run ()
method. It is a natural death.

However, we can kill it by sending the stop message to it at any state thus causing a premature
death. A thread can be killed as soon as it is born, or while it is running, or even when it is in “not
runnable” (blocked) condition.

How To Create a Thread

They can be created by using two different mechanisms


1. Create a class that extends the standard Thread class.

2. Create a class that implements the standard Runnable interface

that is, a thread can be defi ned by extending the java.lang.Thread class or by implementing the
java.lang.Runnable interface .

The run() method should be overridden and should contain the code that will be executed by the
new thread. This method must be public with a void return type and should not take any arguments.

Extending the Thread Class

The steps for creating a thread by using the first mechanism are:

1. Create a class by extending the Thread class and override the run() method:

class MyThread extends Thread

public void run()

// thread body of execution

2. Create a thread object: MyThread thr1 = new MyThread();

3. Start Execution of created thread: thr1.start();


An example program illustrating creation and invocation of a thread object is given below:

/* ThreadEx1.java: A simple program creating and invoking a thread object by extending the
standard Thread class. */

class MyThread extends Thread

public void run()

System.out.println(“ this thread is running ... ”);

class ThreadEx1

public static void main(String [] args )

MyThread t = new MyThread(); t.start();

Implementing the Runnable Interface

The steps for creating a thread by using the second mechanism are:

1. Create a class that implements the interface Runnable and override run() method:

class MyThread implements Runnable

{
public void run()

// thread body of execution

2.Creating Object:

MyThread myObject = new MyThread();

3.Creating Thread Object: Thread thr1 = new Thread(myObject);

4. Start Execution:

thr1.start();

An example program illustrating creation and invocation of a thread object is given below:

/* ThreadEx2.java: A simple program creating and invoking a thread object by implementing


Runnable interface. */

class MyThread implements Runnable

public void run()

System.out.println(“ this thread is running ... ”);

class ThreadEx2

{
public static void main(String [] args )

Thread t = new Thread(new MyThread());

t.start();

Thread class

Thread class provide constructors and methods to create and perform operations on a thread.

Thread class extends Object class and implements Runnable interface.

Commonly used Constructors of Thread class:

● Thread()

● Thread(String name)

● Thread(Runnable r)

● Thread(Runnable r,String name)

Commonly used methods of Thread class

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

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().

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

Naming Thread and Current Thread

Naming Thread

● The Thread class provides methods to change and get the name of a thread. By default, each
thread has a name i.e. thread-0, thread-1 and so on.

● By we can change the name of the thread by using setName() method.

● The syntax of setName() and getName() methods are given below:

1. public String getName(): is used to return the name of a thread.

2. public void setName(String name): is used to change the name of a thread.


class TestMultiNaming1 extends Thread

public void run()

System.out.println("running...");

public static void main(String args[])

TestMultiNaming1 t1=new TestMultiNaming1();

TestMultiNaming1 t2=new TestMultiNaming1(); System.out.println("Name of t1:"+t1.getName());


System.out.println("Name of t2:"+t2.getName());

t1.start();

t2.start();

t1.setName("San");

System.out.println("After changing name of t1:"+t1.getName());

Priority of a Thread (Thread Priority):

● Each thread have a priority.

● Priorities are represented by a number between 1 and 10.

● In most cases, thread schedular schedules the threads according to their priority (known as
preemptive scheduling).
● But it is not guaranteed because it depends on JVM specification that which scheduling it
chooses

3 constants defined in Thread class:

1. public static int MIN_PRIORITY

2. public static int NORM_PRIORITY

3. public static int MAX_PRIORITY

Default priority of a thread is 5 (NORM_PRIORITY).

The value of MIN_PRIORITY is 1 and the value of MAX_PRIORITY is 10.

class TestMultiPriority1 extends Thread

public void run()

System.out.println("running thread nameis:”+Thread.currentThread().getName());


System.out.println("running thread priority is:"+Thread.currentThread().getPriority());

public static void main(String args[])


{

TestMultiPriority1 m1=new TestMultiPriority1();

TestMultiPriority1 m2=new TestMultiPriority1(); m1.setPriority(Thread.MIN_PRIORITY);


m2.setPriority(Thread.MAX_PRIORITY);

m1.start();

m2.start();

Output:

running thread name is:Thread-0

running thread priority is:10

running thread name is:Thread-1

running thread priority is:1

class A extends Thread

public void run()

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

for(int i=1;i<=4;i++)

{
System.out.println("\t From ThreadA: i= "+i);

System.out.println("Exit from A");

class B extends Thread

public void run()

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

for(int j=1;j<=4;j++)

System.out.println("\t From ThreadB: j= "+j);

System.out.println("Exit from B");

class C extends Thread

public void run()

{
System.out.println("Thread C started");

for(int k=1;k<=4;k++)

System.out.println("\t From ThreadC: k= "+k);

System.out.println("Exit from C");

class ThreadPriority

public static void main(String args[])

A threadA=new A();

B threadB=new B();

C threadC=new C();

threadC.setPriority(Thread.MAX_PRIORITY);
threadB.setPriority(threadA.getPriority()+1); threadA.setPriority(Thread.MIN_PRIORITY);

System.out.println("Started Thread A");

threadA.start();

System.out.println("Started Thread B");

threadB.start();

System.out.println("Started Thread C");


threadC.start();

System.out.println("End of main thread");

Daemon Thread in Java

Daemon thread in java is a service provider thread that provides services to the user thread.

● Its life depend on the mercy of user threads i.e. when all the user threads dies, JVM terminates
this thread automatically.

● There are many java daemon threads running automatically e.g. gc, finalizer etc.

● You can see all the detail by typing the jconsole in the command prompt.

● The jconsole tool provides information about the loaded classes, memory usage, running threads
etc
● It provides services to user threads for background supporting tasks. It has no role in life than to
serve user threads.

● Its life depends on user threads.

● It is a low priority thread.

The java.lang.Thread class provides two methods for java daemon thread.

1) public void setDaemon(boolean status) used to mark the current thread as daemon thread or user
thread.

2) public boolean isDaemon() used to check that current is daemon.

ublic class DaemonThread extends Thread

public DaemonThread(String name){

super(name);

public void run()

// Checking whether the thread is Daemon or not

if(Thread.currentThread().isDaemon())

System.out.println(getName() + " is Daemon thread");

}
else

System.out.println(getName() + " is User thread");

public static void main(String[] args)

DaemonThread t1 = new DaemonThread("t1");

DaemonThread t2 = new DaemonThread("t2");

DaemonThread t3 = new DaemonThread("t3");

// Setting user thread t1 to Daemon

t1.setDaemon(true);

// starting first 2 threads

t1.start();

t2.start();
// Setting user thread t3 to Daemon

t3.setDaemon(true);

t3.start();

Output:

t1 is Daemon thread

t3 is Daemon thread

t2 is User thread

Daemon vs. User Threads

 Priority: When only daemon threads remain in a process, the JVM exits. This makes sense
because when only daemon threads are running, there is no need for a daemon thread to
provide a service to another thread.

 Usage: Daemon threads are primarily used to provide background support to user threads.
They handle tasks that support the main execution without interfering with the user’s
operations.

Synchronization in Java

Synchronization in java is the capability to control the access of multiple threads to any shared
resource.

Java Synchronization is better option where we want to allow only one thread to access the shared
resource.

The synchronization is mainly used to

1. To prevent thread interference.

2. To prevent consistency problem


Types of Synchronization

There are two types of synchronization

1. Process Synchronization

2. Thread Synchronization Here, we will discuss only thread synchronization.

Thread Synchronization

There are two types of thread synchronization mutual exclusive and inter-thread communication.

1. Mutual Exclusive

1. Synchronized method.

2. Synchronized block.

3. static synchronization.

2. Cooperation (Inter-thread communication in java)

Mutual Exclusive

Mutual Exclusive helps keep threads from interfering with one another while sharing data.

This can be done by three ways in java:

1. by synchronized method

2. by synchronized block

3. by static synchronisation

By synchronized method

datatype synchronized method


void synchronized print(){ // method is synchronized

// lines of codes

Example:

In the below program, class Table contains one method print() which is not synchronized.
Two threads, t1 and t2 are accessing the same print() method, which print table 4 and 6,
respectively. Let’s see the output we get from the program when the method is not
synchronized.

class Table{

void print(int val){ // method is not synchronized

// In this for loop , table of 4 and 6 is print.

for(int i=1;i<=10;i++){

System.out.println(val*i);

try{

Thread.sleep(1000);

}catch(Exception e){

System.out.println(e);

}
}

//Thread 1 access print() method

class Thread1 extends Thread{

Table t;

Thread1(Table t){

this.t=t;

public void run(){

// access print() method of Table class and pass 4 as argument

t.print(4);

//Thread2 access print() method


class Thread2 extends Thread{

Table t;

Thread2(Table t){

this.t=t;

public void run(){

// access print() method of Table class and pass 6 as argument

t.print(6);

public class Main

public static void main(String[] args) {

Table t=new Table();

Thread1 t1=new Thread1(t);

Thread2 t2=new Thread2(t);

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

In the above program, class Thread1 and Thread2 extends the Thread class. In Thread1 class, the
print() method of Table class is called to print table of 4 and in the Thread2 class, the print() method
of the Table class is called to print a table of 6. Both classes access the print() method
simultaneously. Since the method is not synchronized, we get different outputs.

Output

Here, by observing the outputs, we conclude that if the method is not synchronized, then we get
different outputs, or a data inconsistency problem will occur. So, to resolve this issue, we have to
make a synchronized method. Now let’s make the method synchronized and then see the output.

class Table{

synchronized void print(int val){ // method is synchronized

// In this for loop , table of 4 and 6 is print.


for(int i=1;i<=10;i++){

System.out.println(val*i);

try{

Thread.sleep(1000);

}catch(Exception e){

System.out.println(e);

//Thread 1 access print() method

class Thread1 extends Thread{

Table t;

Thread1(Table t){

this.t=t;
}

public void run(){

// access print() method of Table class and pass 4 as argument

t.print(4);

//Thread2 access print() method

class Thread2 extends Thread{

Table t;

Thread2(Table t){

this.t=t;

public void run(){

// access print() method of Table class and pass 4 as argument


t.print(6);

public class Main

public static void main(String[] args) {

Table t=new Table();

Thread1 t1=new Thread1(t);

Thread2 t2=new Thread2(t);

t1.start();

t2.start();

}
By synchronized block

Example:

Let’s take the same example again of table printing, but instead of making the method
synchronized, we make a synchronized block inside the print () method to print tables 4 and
6. We always get the same output as the thread is synchronized, and at a time, one thread
enters inside the synchronized block to execute the code.

class Table{

void print(int val){

synchronized(this){ // block is synchronized

for(int i=1;i<=10;i++){

System.out.println(val*i);

try{
Thread.sleep(1000);

}catch(Exception e){

System.out.println(e);

//Thread 1 access print() method

class Thread1 extends Thread{

Table t;

Thread1(Table t){

this.t=t;

}
public void run(){

t.print(4);

//Thread2 access print() method

class Thread2 extends Thread{

Table t;

Thread2(Table t){

this.t=t;

public void run(){

t.print(6);

public class Main

{
public static void main(String[] args) {

Table t=new Table();

Thread1 t1=new Thread1(t);

Thread2 t2=new Thread2(t);

t1.start();

t2.start();

Here, we also get the same output that we get in the synchronized method.

Output:

static synchronized block or method.

As we know, in Java, each object has one lock, which means at a time, one thread can execute a
block of code on a given instance of the class. So, if we have multiple objects in our program, then
multiple locks are there, and if we are dealing with multiple threads in our program, then the output
may be corrupt, and a data inconsistency problem will occur. To resolve such a problem, we use a
static synchronized block or method.

Example

Let’s assume Shubham, Vikram, Inder, and Kamlesh have a joint account in a bank. The total
balance in the bank account is 10000 rupees. If Shubham or Vikram withdraws 10000, then the
amount withdrawn by any of the rest is zero or insufficient balance is shown to them, but Inder or
Kamlesh is still able to withdraw 10000 rupees, and vice-versa, this shows the program is incorrect
and inconsistent or corrupt data is given by the program. Go through the below program to get
clarity on the above mentioned example.
In the below program, two objects are created b1 and b2. For one object, one lock is there, which
means at a time one thread is allowed to execute the method, so, for two objects, two locks are
there, which allow two threads to execute the same method simultaneously; hence we get corrupt
output, or 20000 amount is withdrawn in spite of having only 10000 amounts in Bank.

class Bank extends Thread{

int available_balance=10000;

int withdrawl_balance;

Bank(int withdrawl_balance){

this.withdrawl_balance=withdrawl_balance;

public synchronized void withdraw(){ // method is synchronized

String thread_name=currentThread().getName();

if(withdrawl_balance<=available_balance){

System.out.println(thread_name+""+" withdrawmoney:"+""+withdrawl_balance);

available_balance-=withdrawl_balance;

}else{

System.out.println(thread_name+" your account has Insufficient balance");


}

public void run(){

withdraw();

public class Main

public static void main(String[] args) {

Bank b1=new Bank(10000); // has one lock

Thread t1=new Thread(b1);

t1.setName("Shubham");

Thread t2=new Thread(b1);

t2.setName("Vikram");

t1.start();

t2.start();
Bank b2=new Bank(10000);// has one lock

Thread t3=new Thread(b2);

Thread t4=new Thread(b2);

t3.setName("Inder");

t4.setName("Kamlesh");

t3.start();

t4.start();

Output:

Now, if we make method static synchronized then one thread execute method at a time and we get
consistent data.

class Bank extends Thread{

static int available_balance=10000;


static int withdrawl_balance;

Bank(int withdrawl_balance){

this.withdrawl_balance=withdrawl_balance;

public static synchronized void withdraw(){ // method is static synchronized

String thread_name=currentThread().getName();

if(withdrawl_balance<=available_balance){

System.out.println(thread_name+""+" withdraw money:"+""+withdrawl_balance);

available_balance-=withdrawl_balance;

}else{

System.out.println(thread_name+" your account has Insufficient balance");

public void run(){

withdraw();

}
public class Main

public static void main(String[] args) {

Bank b1=new Bank(10000); // has one lock

Thread t1=new Thread(b1);

t1.setName("Shubham");

Thread t2=new Thread(b1);

t2.setName("Vikram");

t1.start();

t2.start();

Bank b2=new Bank(10000);// has one lock

Thread t3=new Thread(b2);

Thread t4=new Thread(b2);

t3.setName("Inder");

t4.setName("Kamlesh");

t3.start();

t4.start();
}

Output:

Two Threads Printing a Multiplication Table Using Synchronized Method

class MultiplicationTable {

// Synchronized method to print multiplication table

public synchronized void printTable(int number) {

for (int i = 1; i <= 10; i++) {

System.out.println(number + " * " + i + " = " + (number * i));

try {

Thread.sleep(100); // Adding delay to simulate real-time scenario

} catch (InterruptedException e) {

e.printStackTrace();
}

class TableThread extends Thread {

private MultiplicationTable table;

private int number;

public TableThread(MultiplicationTable table, int number) {

this.table = table;

this.number = number;

@Override

public void run() {

table.printTable(number);

public class Main {

public static void main(String[] args) {

MultiplicationTable table = new MultiplicationTable();


// Creating two threads that will print multiplication tables

TableThread thread1 = new TableThread(table, 5);

TableThread thread2 = new TableThread(table, 7);

thread1.start();

thread2.start();

try {

thread1.join();

thread2.join();

} catch (InterruptedException e) {

e.printStackTrace();

Two Threads Printing a Multiplication Table Using Synchronized Block

class MultiplicationTable {

// Method to print multiplication table using synchronized block

public void printTable(int number) {

synchronized(this) {
for (int i = 1; i <= 10; i++) {

System.out.println(number + " * " + i + " = " + (number * i));

try {

Thread.sleep(100); // Adding delay to simulate real-time scenario

} catch (InterruptedException e) {

e.printStackTrace();

class TableThread extends Thread {

private MultiplicationTable table;

private int number;

public TableThread(MultiplicationTable table, int number) {

this.table = table;

this.number = number;

}
@Override

public void run() {

table.printTable(number);

public class Main {

public static void main(String[] args) {

MultiplicationTable table = new MultiplicationTable();

// Creating two threads that will print multiplication tables

TableThread thread1 = new TableThread(table, 5);

TableThread thread2 = new TableThread(table, 7);

thread1.start();

thread2.start();

try {

thread1.join();

thread2.join();

} catch (InterruptedException e) {
e.printStackTrace();

Inter-thread communication in Java Inter-thread communication or Co-operation: It is all


about allowing synchronized threads to communicate with each other

Cooperation (Inter-thread communication)

It 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:

● wait()

● notify()

● notifyAll()

1) 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.

public final void wait()throws InterruptedException

waits until object is notified.

public final void wait(long timeout)throws InterruptedException


waits for the specified amount of time.

2) 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.

Syntax: public final void notify()

3) notifyAll() method

Wakes up all threads that are waiting on this object's monitor.

Syntax: public final void notifyAll()

Example:

class SumNum extends Thread

int sum=0;

public void run()

synchronize(this)

{
for(int i=1;i<=5;i++)

sum=sum+I;

this.notify();

public class InterThreadCommunication

public static void main(String arg[])

SumNum th=new SumNum()

th.strat();

synchronize(Th.)

th.wait();

System.out.println(“Sum is : ”+ th.sum());

}
The java.io package in Java provides a comprehensive set of input and output (I/O) streams for
reading from and writing to data sources, such as files, network connections, memory buffers, and
more. It includes classes for handling both byte and character data, as well as utility classes for
performing various I/O operations.

There are two primary types of streams in Java:

Byte Streams and Character Streams. Each type has different classes and methods for handling
data. Here's a detailed breakdown:

1. Byte Streams

Byte streams (InputStream and OutputStream) are used to handle input and output of raw binary
data in Java. They operate with bytes, which can represent any kind of data, including text and
binary data (image files, audio files, etc)

• InputStream: This is the base class for all byte input streams. It provides methods to read
bytes from a source.

• FileInputStream

• ByteArrayInputStream

• BufferedInputStream

• DataInputStream

• ObjectInputStream

• OutputStream: This is the base class for all byte output streams. It provides methods to
write bytes to a destination.

• FileOutputStream

• ByteArrayOutputStream

• BufferedOutputStream

• DataOutputStream
• ObjectOutputStream

Example: Reading and Writing with Byte Streams

Let's look at an example where we read from a file using FileInputStream (a byte stream) and
write to a file using FileOutputStream (a byte stream).

Example Code

import java.io.*;

public class ByteStreamsExample {


public static void main(String[] args) {
// Example 1: Reading from a file using FileInputStream
try (FileInputStream fis = new FileInputStream("input.txt")) {
int data;
while ((data = fis.read()) != -1) {
System.out.print((char) data); // Convert byte to char for text
}
} catch (IOException e) {
e.printStackTrace();
}

// Example 2: Writing to a file using FileOutputStream


try (FileOutputStream fos = new FileOutputStream("output.txt")) {
String text = "Hello, Byte Streams!";
byte[] bytes = text.getBytes(); // Convert string to bytes
fos.write(bytes); // Write bytes to file
} catch (IOException e) {
e.printStackTrace();
}
}
}

Explanation

1. Reading with FileInputStream:


o We use FileInputStream to read bytes from the file input.txt.
o Inside the while loop, fis.read() reads bytes from the file, and data holds each
byte read.
o (char) data converts each byte to its corresponding character and prints it.
2. Writing with FileOutputStream:
o We create a string text.
o text.getBytes() converts the string into bytes.
o fos.write(bytes) writes the bytes to the file output.txt.

Output

Assume input.txt contains the text "Hello, Byte Streams!". After running the program,
output.txt will be created with the same content.
2. Character Streams

Character streams (Reader and Writer) are used to handle input and output of characters,
specifically for text-based data. They handle Unicode characters and are more suitable for working
with text files.

• Reader: This is the base class for all character input streams. It provides methods to read
characters from a source.

• FileReader

• StringReader

• BufferedReader

• InputStreamReader

• CharArrayReader

• PipedReader

• Writer: This is the base class for all character output streams. It provides methods to write
characters to a destination.

• FileWriter

• StringWriter

• BufferedWriter

• OutputStreamWriter

• CharArrayWriter

• PipedWriter

Example Code
import java.io.*;

public class CharacterStreamsExample {


public static void main(String[] args) {
// Example 1: Reading from a file using FileReader
try (FileReader reader = new FileReader("input.txt")) {
int data;
while ((data = reader.read()) != -1) {
System.out.print((char) data); // Print each character
}
} catch (IOException e) {
e.printStackTrace();
}

// Example 2: Writing to a file using FileWriter


try (FileWriter writer = new FileWriter("output.txt")) {
String text = "Hello, Character Streams!";
writer.write(text); // Write text to file
} catch (IOException e) {
e.printStackTrace();
}
}
}

Explanation

1. Reading with FileReader:


o FileReader reads characters from the file input.txt.
o Inside the while loop, reader.read() reads characters as integers (data).
o (char) data converts each integer data to its corresponding character and prints it.
2. Writing with FileWriter:
o We create a string text.
o writer.write(text) writes the string to the file output.txt.

Output

Assume input.txt contains the text "Hello, Character Streams!". After running the program,
output.txt will be created with the same content.

Key Differences

 Byte Streams: Handle binary data, suitable for any type of data including text and non-text.
 Character Streams: Handle text-based data, handle Unicode characters, and are
specifically designed for reading and writing textual information.
Byte Stream Vs Character Stream In Java :

Byte Stream Character Stream

They process the data byte by byte. They process the data character by character.

They read/write data 16 bits maximum at a


They read/write data 8 bits maximum at a time.
time.

They are most suitable to process binary files. They are most suitable to process text files.

All byte stream classes in Java are descendants of All character stream classes in Java are
InputStream and OutputStream. descendants of Reader and Writer.

You might also like