Unit 3 - Java
Unit 3 - Java
Multiprocessing in Java
Multiprocessing in Java is purely based on the number of processors available on
the host computer. Every process initiated by the user is sent to the CPU
(processor). It loads the registers on the CPU with the data related to the assigned
process.
To perform multiprocessing in Java, the user needs one processor. Therefore, when
the user requests the simultaneous execution of the second process, the alternate
CPU core gets triggered and executes the process.
Multithreading in Java
Multithreading in Java is a similar approach to multiprocessing. However, there are
some fundamental differences between the two. Instead of a physical processor,
multithreading involves virtual and independent threads.
It assigns each process with one or more threads based on their complexity. Each
thread is virtual and independent of the other. This makes process execution much
safer. If a thread or two are terminated during an unexpected situation, the process
execution will not halt.
So far, you have looked into the process, thread, and thread lifecycle. You
understood that a computer executes a task via a thread. Now, you will learn the
process of multithreading in Java.
As the name suggests, multithreading in Java executes a complex process by
running a collection of threads simultaneously. Each thread belongs to the
Java.lang.Thread class. The thread class overrides the run() method and executes
the process.
Methods of Multithreading in Java
Following are the methods for Multithreading in Java.
on higher priority
After the methods of Multithreading in Java, you will go through an example based
on Multithreading in Java.
Multithreading
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.
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)
• Threads share the same address space.
• A thread is lightweight.
• Cost of communication between the thread is low.
Thread
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.
As shown in the above figure, a thread is executed inside the process. There is
context- switching between the threads. There can be multiple processes inside
the OS, and one process can have multiple threads.
Note: At a time one thread is executed only.
Thread class
Java provides Thread class to achieve thread programming. Thread class
provides constructors and methods to create and perform operations on a thread.
Thread class extends Object class and implements Runnable interface
Life cycle of a Thread (Thread States)
A thread can be in one of the five states. According to sun, there is only 4
states in thread life cycle in java new, runnable, non-runnable and terminated.
There is no running state.
But for better understanding the threads, we are explaining it in the 5 states.
The life cycle of the thread in java is controlled by JVM. The java thread states
are as follows:
• New
• Runnable
• Running
• Non-Runnable (Blocked)
• Terminated
There are two ways to create a thread:
• By extending Thread class
• By implementing Runnable interface.
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:
• public void run(): is used to perform action for a thread.
• public void start(): starts the execution of the thread.JVM calls the
run() method on the thread.
• public void sleep(long miliseconds): Causes the currently executing
thread to sleep (temporarily cease execution) for the specified number of
milliseconds.
• public Thread currentThread(): returns the reference of currently
executing thread.
• public void yield(): causes the currently executing thread object to
temporarily pause and allow other threads to execute.
• public void suspend(): is used to suspend the thread(depricated).
• public void resume(): is used to resume the suspended thread(depricated).
• public void stop(): is used to stop the thread(depricated).
• public boolean isDaemon(): tests if the thread is a daemon thread.
• public void interrupt(): interrupts the thread.
• public boolean isInterrupted(): tests if the thread has been interrupted.
• public static boolean interrupted(): tests if the current thread has
been interrupted.
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().
void run(): is used to perform action for a thread.
Starting a thread:
start() method of Thread class is used to start a newly created thread. It
performs following tasks:
• A new thread starts(with new callstack).
• The thread moves from New state to the Runnable state.
• When the thread gets a chance to execute, its target run() method will run.
{
System.out.println("thread is running...");
}
public static void main(String args[])
{
Multi3 m1=new Multi3();
Thread t1 =new
Thread(m1); t1.start();
}
}
Output:thread is running...
If you are not extending the Thread class,your class object would not
be treated as a thread object.So you need to explicitely create Thread
class object.We are passing the object of your class that implements
Runnable so that your class run() method may execute.
1) New
The thread is in new state if you create an instance of Thread class but before
the invocation of start() method.
2) Runnable
The thread is in runnable state after invocation of start() method, but the
thread scheduler has not selected it to be the running thread.
3) Running
The thread is in running state if the thread scheduler has selected it.
4) Non-Runnable (Blocked)
This is the state when the thread is still alive, but is currently not eligible to run.
5) Terminated
A thread is in terminated or dead state when its run() method exits
SYNCHRONIZATION–USING SYNCHRONIZED METHODS– USING
SYNCHRONIZED STATEMENT
Java, synchronization is used to control access to a shared resource by multiple
threads, ensuring that only one thread can access the resource at a time. This is
especially important in multithreaded environments where threads may conflict
and cause data corruption or unexpected behavior if they access the same resource
simultaneously.
Java provides two ways to implement synchronization: using synchronized
methods and synchronized blocks (statements). Here’s an explanation of both:
1. Synchronized Methods
A synchronized method is a method that is declared with the synchronized
keyword. When a thread calls this method, it acquires a lock (or monitor) on the
object the method belongs to. This means that only one thread can execute this
method at a time for that particular object.
Example of Synchronized Method:
class Counter {
private int count = 0;
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
INTERTHREAD COMMUNICATION
class SharedResource {
private int data = 0;
private boolean available = false;
// Producer thread
Thread producer = new Thread(() -> {
try {
for (int i = 1; i <= 5; i++) {
resource.produce(i);
Thread.sleep(1000); // Simulate time taken to produce
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// Consumer thread
Thread consumer = new Thread(() -> {
try {
for (int i = 1; i <= 5; i++) {
resource.consume();
Thread.sleep(2000); // Simulate time taken to consume
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
producer.start();
consumer.start();
}
}
Explanation:
The SharedResource class has a shared resource (data) that the producer
and consumer access.
The producer produces data, while the consumer consumes it.
wait() is used in the consume() method, where the consumer waits until data
is available. It waits on the object (the shared resource) and releases the lock.
DEPARTMENT OF COMPUTRE SCIENCE –RAAK-Mrs.R SUGANYA -AP Page 16
23UBCAC43/23UCSCC43 – PROGRAMMING IN JAVA UNIT 3
notify() is used in the produce() method to notify the consumer that data is
available for consumption.
If the consumer tries to consume before the producer produces data, it will
wait until the producer notifies it.
Flow of Execution:
1. The producer thread produces data and sets the available flag to true, then
calls notify().
2. The consumer thread consumes the data, setting the available flag to false,
then calls notify().
3. If the producer has produced data, the consumer can consume it. If not, the
consumer will wait.
4. The producer waits if the consumer hasn't consumed the data.
Important Notes:
Deadlock: It is important to carefully design the wait-notify interactions to
avoid deadlocks, where two threads are waiting indefinitely for each other to
release resources.
wait() and notify() must be used inside synchronized blocks or methods
because they need to acquire the intrinsic lock of the object before they can
be called.
Key Takeaways:
wait(): Puts the current thread into a waiting state until it's notified.
notify(): Wakes up one thread waiting on the same object.
notifyAll(): Wakes up all threads waiting on the same object.
Interthread communication using wait() and notify() is crucial in scenarios where
threads must cooperate or synchronize their actions.
class Resource2 {
synchronized void method2(Resource1 r1) {
System.out.println("Thread 2: Holding Resource 2...");
try { Thread.sleep(1000); } catch (InterruptedException e) {}
System.out.println("Thread 2: Waiting for Resource 1...");
r1.lastMethod(); // Calling method of Resource1
}
t1.start();
t2.start();
}
}
How Deadlock Occurs in the Above Example:
1. Thread 1 acquires a lock on Resource1 and then tries to acquire a lock on
Resource2.
2. Thread 2 acquires a lock on Resource2 and tries to acquire a lock on
Resource1.
3. Now, Thread 1 is waiting for Thread 2 to release Resource2, and Thread 2
is waiting for Thread 1 to release Resource1.
4. Neither thread can proceed, resulting in a deadlock.
Ways to Avoid Deadlock:
1. Lock Ordering (Prevention):
o Establish a global order in which locks must be acquired by all
threads. If all threads acquire locks in the same order, circular waiting
is prevented.
synchronized (Resource1.class) {
synchronized (Resource2.class) {
DEPARTMENT OF COMPUTRE SCIENCE –RAAK-Mrs.R SUGANYA -AP Page 19
23UBCAC43/23UCSCC43 – PROGRAMMING IN JAVA UNIT 3
scanner.close();
}
}
Scanner is easy to use and provides methods like nextInt(), nextLine(), etc.,
for different types of input.
Using BufferedReader for console input:
import java.io.*;
try {
FileWriter writer = new FileWriter("output.txt");
writer.write("Hello, this is a test file.");
writer.close();
System.out.println("Data written to file.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
FileWriter writes characters to a file. It creates the file if it does not exist
and overwrites it if the file already exists.
Using BufferedWriter (more efficient):
import java.io.*;
try {
FileReader reader = new FileReader("input.txt");
int character;
while ((character = reader.read()) != -1) {
System.out.print((char) character);
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
FileReader reads characters from a file.
Using BufferedReader for efficient file reading:
import java.io.*;