UNIT III Multithreaded
UNIT III Multithreaded
1
2
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
Multitasking
Multitasking is a process of executing multiple tasks simultaneously. We use multitasking to utilize the
CPU. Multitasking can be achieved in two ways:
o Process-based Multitasking (Multiprocessing)
o Thread-based Multitasking (Multithreading)
1) Process-based Multitasking (Multiprocessing)
o Each process has an address in memory. In other words, each process allocates a separate memory
area.
o A process is heavyweight.
o Cost of communication between the process is high.
o Switching from one process to another requires some time for saving and loading registers, memory
maps, updating lists, etc.
2) Thread-based Multitasking (Multithreading)
o Threads share the same address space.
o A thread is lightweight.
o Cost of communication between the thread is low.
Note: At least one process is required for each thread.
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.
Java 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.
Thread States:
o New: A thread that has been created but not yet started.
o Runnable: A thread that is ready to run and waiting for CPU time.
o Blocked: A thread that is blocked waiting for a monitor lock.
o Waiting: A thread that is waiting indefinitely for another thread to perform a particular action.
o Timed Waiting: A thread that is waiting for another thread to perform an action for up to a
specified waiting time.
o Terminated: A thread that has completed its task.
Life cycle of a Thread (Thread States)
In Java, a thread always exists in any one of the following states. These states are:
1. New
2. Active
o Runnable
o Running
3. Blocked / Waiting(Non-runnable state)
4. Timed Waiting
5. Terminated (Dead)
3
Explanation of Different Thread States
New: Whenever a new thread is created, it is always in the new state. For a thread in the new state, the code
has not been run yet and thus has not begun its execution.
Active: When a thread invokes the start() method, it moves from the new state to the active state. The
active state contains two states within it: one is runnable, and the other is running.
o Runnable: A thread, that is ready to run is then moved to the runnable state. In the runnable state,
the thread may be running or may be ready to run at any given instant of time.
o Running: When the thread gets the CPU, it moves from the runnable to the running state.
Generally, the most common change in the state of a thread is from runnable to running and again
back to runnable.
Blocked or Waiting: Whenever a thread is inactive for a span of time then, either the thread is in the
blocked state or is in the waiting state
Timed Waiting: Sometimes, waiting for leads to starvation. For example, a thread (its name is A) has
entered the critical section of a code and is not willing to leave that critical section. A real example of timed
waiting is when we invoke the sleep() method on a specific thread. The sleep() method puts the thread in
the timed wait state.
Terminated: A thread reaches the termination state because of the following reasons:
o When a thread has finished its job, then it exists or terminates normally.
o Abnormal termination: It occurs when some unusual events such as an unhandled exception or
segmentation fault.
A terminated thread means the thread is no more in the system.
The following diagram shows the different states involved in the life cycle of a thread.
A thread is a path of execution in a program that enters any one of the following five states during its life
cycle. The five states are as follows:
1. New State:
When we create a thread object using the Thread class, the thread is born and enters the New state. This
means the thread is created, but the start() method has not yet been called on the instance.
2. Runnable State:
Runnable state means a thread is ready for execution of any statement. When the start() method is called on
a new thread, thread enters into from New to a Runnable state.
3. Running State:
4
Running means Processor (CPU) has allocated time slot to thread for its execution. When thread scheduler
selects a thread from the runnable state for execution, it goes into running state. Look at the above figure.
his code is an example of thread’s state transitions. When start() method is called, the thread enters the
Runnable state. Once the CPU schedules it, the thread transitions to the Running state, executing the run()
method.
A running thread may give up its control in any one of the following situations and can enter into the
blocked state.
a) 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.
b) 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.
c) 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.
4. 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.
For example, a thread enters the Blocked state when it is waiting to acquire a lock or a monitor that is held
by another thread. This generally happens when multiple threads are trying to access a synchronized block
or method.
5
State Description
New Thread is created but not started (Thread t = new Thread();)
Runnable Thread is ready to run but waiting for CPU time (start())
Running Thread is currently executing
Blocked Thread is waiting for a resource (e.g., file I/O, synchronized block)
Waiting Thread is indefinitely waiting (wait())
Timed Waiting Thread waits for a specific time (sleep(1000))
Terminated Thread has completed execution
Method Description
start() Starts a new thread and calls run()
run() Defines the code executed by the thread
sleep(ms) Makes the thread sleep for ms milliseconds
join() Waits for the thread to complete
interrupt() Interrupts the thread execution
setPriority(int) Sets thread priority (1 to 10)
getName() Returns the thread name
setName(String) Sets a custom name for the thread
isAlive() Checks if the thread is still running
6
}
5. Synchronization in Java
🔹 Why Synchronization?
Prevents race conditions when multiple threads access shared resources.
Ensures only one thread modifies a critical section at a time.
🔹 Using Synchronized Methods
class SharedResource {
synchronized void printNumbers(int n) {
for (int i = 1; i <= 5; i++) {
System.out.println(n * i);
try {
Thread.sleep(500);
} catch (Exception e) {
System.out.println(e);
}
}
}
}
7
for (int i = 1; i <= 5; i++) {
System.out.println(n * i);
try { Thread.sleep(500); } catch (Exception e) {}
}
}
}
}
📌 Synchronized blocks improve efficiency by reducing the locked code region.
6. Inter-Thread Communication
🔹 Using wait(), notify(), and notifyAll()
wait(): Makes a thread pause execution until another thread notifies it.
notify(): Wakes up one waiting thread.
notifyAll(): Wakes up all waiting threads.
🔹 Example
class SharedResource {
synchronized void produce() {
System.out.println("Producing...");
try {
wait(); // Thread waits
} catch (Exception e) {}
System.out.println("Resumed...");
}
7. Deadlock in Java
🔹 What is a Deadlock?
Occurs when two or more threads wait indefinitely for each other to release a lock.
🔹 Example of Deadlock
class DeadlockExample {
8
static final Object lock1 = new Object();
static final Object lock2 = new Object();
t1.start();
t2.start();
}
}
Creating Threads in Java (Simplified Explanation)
Java provides two ways to create a thread:
1️⃣ By implementing the Runnable interface
2️⃣ By extending the Thread class
9
} catch (InterruptedException e) {
System.out.println(e);
}
}
}
}
10
It keeps your design cleaner by separating the "task" (Runnable) from the "execution" (Thread).
11
available = true;
notify();
}
12
🔍 Key Points:
Use volatile boolean flag to control execution.
Avoid stop() as it may leave shared resources in an inconsistent state.
💡 Summary:
Use priorities (setPriority()) but don’t rely on them.
Use synchronization (synchronized) to avoid data inconsistency.
Use wait() & notify() for better thread communication.
Avoid deadlocks by managing resource locks properly.
Use modern thread control (volatile flag) instead of old methods.
t1.setPriority(Thread.MIN_PRIORITY); // Priority 1
t2.setPriority(Thread.MAX_PRIORITY); // Priority 10
t1.start();
t2.start();
}
}
Output
Thread-0 - Priority: 1
15
Thread-1 - Priority: 10
(Note: The execution order is not guaranteed.)
6. Daemon Threads
A daemon thread is a background thread that automatically terminates when all user threads finish
execution.
class DaemonExample extends Thread {
public void run() {
while (true) {
System.out.println("Daemon thread running...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Interrupted");
}
}
}
}
public class DaemonThreadExample {
public static void main(String[] args) {
DaemonExample daemon = new DaemonExample();
daemon.setDaemon(true); // Set the thread as a daemon thread
daemon.start();
7. Summary
The Thread class provides methods to create and manage threads in Java.
The start() method initiates a new thread, while run() contains the thread’s logic.
The sleep(), join(), and yield() methods control thread execution.
The synchronized keyword ensures thread-safe execution.
Thread priority can be set but is OS-dependent.
16
Daemon threads run in the background and terminate when all user threads finish.
Runnable Interface
t1.start();
t2.start();
}
}
Output (Avoids race conditions)
Thread-0 - Count: 1
Thread-0 - Count: 2
Thread-1 - Count: 3
Thread-1 - Count: 4
...
t1.start();
t2.start();
}
}
Synchronization in Java
1. Why Synchronization?
When multiple threads access a shared resource (e.g., a variable or an object), data inconsistency can
occur. Synchronization ensures that only one thread accesses the critical section of the code at a time.
3. Synchronized Methods
A synchronized method ensures that only one thread can execute it at a time.
Example: Synchronizing a Counter
class Counter {
private int count = 0;
// Synchronized method
public synchronized void increment() {
count++;
System.out.println(Thread.currentThread().getName() + " - Count: " + count);
}
}
class MyThread extends Thread {
Counter counter;
public MyThread(Counter counter) {
this.counter = counter;
}
public void run() {
for (int i = 0; i < 5; i++) {
counter.increment();
19
try {
Thread.sleep(100);
} catch (InterruptedException e) {
System.out.println("Interrupted");
}
}
}
}
public class SyncExample {
public static void main(String[] args) {
Counter counter = new Counter();
t1.start();
t2.start();
}
}
Output (Ensures proper count updates)
Thread-0 - Count: 1
Thread-1 - Count: 2
Thread-0 - Count: 3
Thread-1 - Count: 4
...
4. Synchronized Blocks
Instead of synchronizing the entire method, we can synchronize only the critical section of the code.
Example: Synchronizing a Block of Code
class Counter {
private int count = 0;
5. Static Synchronization
If a method is static, synchronization applies to the class level, not the instance level. This means only one
thread can access a static synchronized method across all instances.
20
Example: Synchronizing a Static Method
class SharedResource {
private static int count = 0;
t1.start();
t2.start();
}
}
Static synchronization is needed when:
✔ A shared resource is static (belongs to the class, not instances).
✔ We need to synchronize across all objects of the class.
Synchronized Methods
21
public void increment() { // Not synchronized
count++;
System.out.println(Thread.currentThread().getName() + " - Count: " + count);
}
}
t1.start();
t2.start();
}
}
Output (Data inconsistency)
Thread-0 - Count: 1
Thread-1 - Count: 2
Thread-0 - Count: 3
Thread-1 - Count: 3 <-- Wrong count!
...
Here, multiple threads modify the count variable simultaneously, causing inconsistent results.
// Synchronized method
public synchronized void increment() {
count++;
System.out.println(Thread.currentThread().getName() + " - Count: " + count);
}
}
t1.start();
t2.start();
}
}
Output (Correct Count)
Thread-0 - Count: 1
Thread-0 - Count: 2
Thread-0 - Count: 3
Thread-1 - Count: 4
Thread-1 - Count: 5
...
Now, one thread completes execution before the next thread can access increment(), ensuring correct
values.
23
5. Important Points About Synchronized Methods
✅ A synchronized method locks the entire object (this), allowing only one thread to access it at a time.
✅ If one thread is inside a synchronized method, other threads must wait until the method is free.
✅ It is useful for instance methods that modify shared data.
t1.start();
t2.start();
}
}
Why Use Static Synchronization?
✔ Locks the entire class, ensuring that multiple threads do not access the static method at the same
time.
✔ Prevents data corruption in shared static resources.
24
8. Alternative: Using Synchronized Blocks
If synchronizing the entire method is too restrictive, use synchronized blocks instead.
class Counter {
private int count = 0;
t1.start();
t2.start();
}
}
Output (Incorrect Count due to Race Condition)
Thread-0 - Count: 1
Thread-1 - Count: 2
Thread-0 - Count: 3
Thread-1 - Count: 3 <-- Wrong count!
...
Multiple threads are modifying count simultaneously, leading to incorrect results.
t1.start();
t2.start();
}
}
Output (Correct Count)
Thread-0 - Count: 1
Thread-1 - Count: 2
Thread-0 - Count: 3
Thread-1 - Count: 4
Thread-0 - Count: 5
...
Here, the synchronized block ensures that only one thread modifies count at a time.
27
synchronized (this) { // Only this block is synchronized
if (balance >= amount) {
System.out.println(Thread.currentThread().getName() + " is withdrawing: " + amount);
balance -= amount;
System.out.println("Remaining Balance: " + balance);
} else {
System.out.println(Thread.currentThread().getName() + " - Not enough balance!");
}
}
}
}
t1.start();
t2.start();
}
}
Output
Thread-0 is withdrawing: 600
Remaining Balance: 400
Thread-1 - Not enough balance!
🔹 Here, the synchronized block ensures that only one user withdraws at a time, preventing over-
withdrawal.
t1.start();
t2.start();
}
}
✔ Class-level synchronization ensures that even if multiple objects exist, only one thread can execute the
block.
Inter-Thread Communication
1. What is Inter-Thread Communication?
Inter-thread communication allows multiple threads to communicate and coordinate their actions in a
synchronized manner. It is primarily used in producer-consumer problems, where one thread produces
data, and another thread consumes it.
🔹 Key Methods for Inter-Thread Communication (From Object Class)
Method Description
wait() Causes the thread to wait until another thread calls notify() or notifyAll().
notify() Wakes up a single thread that is waiting on the object's monitor.
29
Method Description
notifyAll() Wakes up all threads that are waiting on the object's monitor.
💡 These methods must be called inside a synchronized block or synchronized method; otherwise, Java
will throw IllegalMonitorStateException.
// Producer method
public synchronized void produce(int value) {
while (available) { // If data is already available, wait
try {
wait(); // Wait until consumer consumes the data
} catch (InterruptedException e) {
System.out.println("Producer interrupted");
}
}
data = value;
System.out.println("Produced: " + value);
available = true;
notify(); // Notify the consumer that data is ready
}
// Consumer method
public synchronized void consume() {
while (!available) { // If no data is available, wait
try {
wait(); // Wait until producer produces data
} catch (InterruptedException e) {
System.out.println("Consumer interrupted");
}
}
System.out.println("Consumed: " + data);
available = false;
notify(); // Notify producer that data is consumed
}
}
// Producer Thread
class Producer extends Thread {
SharedResource resource;
30
}
// Consumer Thread
class Consumer extends Thread {
SharedResource resource;
// Main Class
public class InterThreadCommunication {
public static void main(String[] args) {
SharedResource resource = new SharedResource();
Producer producer = new Producer(resource);
Consumer consumer = new Consumer(resource);
producer.start();
consumer.start();
}
}
class SharedResource {
private int data;
private boolean available = false;
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
6. Summary
Feature wait() & notify() Lock & Condition
Synchronization Uses intrinsic object lock Uses explicit lock (ReentrantLock)
notify() wakes only one thread, notifyAll() More precise control with signal() and
Wake-Up Control
wakes all signalAll()
Performance Slightly slower due to JVM overhead Faster and more efficient
Use Case Simple producer-consumer problems Complex synchronization scenarios
Deadlock in Java
Deadlock
A deadlock occurs in a multi-threaded program when two or more threads are waiting for each other to
release resources, and none can proceed. This leads to an infinite waiting state, effectively freezing the
program.
33
Neither thread can proceed, causing a deadlock.
threadA.start();
threadB.start();
34
}
}
synchronized (SharedResource.RESOURCE_2) {
System.out.println("Thread A: Locked RESOURCE_2");
}
}
}
}
synchronized (SharedResource.RESOURCE_2) {
System.out.println("Thread B: Locked RESOURCE_2");
}
}
}
}
Now, both threads lock resources in the same order, preventing a cycle.
35
I/O Streams in Java
1. What are I/O Streams?
I/O (Input/Output) streams in Java allow programs to read data from and write data to different sources,
such as files, memory, or network connections.
🔹 Types of Streams in Java
Java provides two main categories of I/O streams:
1. Byte Streams (InputStream & OutputStream) → Handle binary data (e.g., images, audio files).
2. Character Streams (Reader & Writer) → Handle text data (e.g., .txt, .csv files).
36
Class Description
FileReader Reads character data from a file.
FileWriter Writes character data to a file.
BufferedReader Improves reading performance using buffering.
BufferedWriter Enhances writing efficiency.
🔹 Example: Reading a File Using Character Stream
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
// Serialization
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file))) {
Person p = new Person("John Doe", 30);
oos.writeObject(p);
} catch (IOException e) {
System.out.println("Error writing object: " + e.getMessage());
}
// Deserialization
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) {
Person p = (Person) ois.readObject();
System.out.println("Deserialized Person: " + p.name + ", Age: " + p.age);
} catch (IOException | ClassNotFoundException e) {
System.out.println("Error reading object: " + e.getMessage());
}
}
}
Stores and retrieves Java objects from files.
1. Introduction to Streams
Java uses streams to perform input and output (I/O) operations efficiently. Streams provide a common
interface for reading and writing data between different sources (files, memory, network, etc.).
Byte Streams – Handle binary data (images, audio, etc.).
42
Character Streams – Handle text data (Unicode characters).
int byteData;
while ((byteData = fis.read()) != -1) { // Read byte by byte
fos.write(byteData); // Write byte by byte
}
System.out.println("File copied successfully using Byte Streams.");
} catch (IOException e) {
System.out.println("Error: " + e.getMessage());
}
}
}
📝 Explanation:
Reads data byte by byte.
Suitable for binary files (e.g., images, videos).
Uses FileInputStream and FileOutputStream.
7. Summary
Feature Byte Stream Character Stream
Data Type Binary (0s and 1s) Text (Unicode characters)
Used For Images, audio, video Text, documents
Example Classes FileInputStream, FileOutputStream FileReader, FileWriter
Buffered Variant BufferedInputStream, BufferedOutputStream BufferedReader, BufferedWriter
1. Introduction
Java provides multiple ways to read input from the console, which is useful for interacting with users.
The main methods include:
1. Using Scanner (Preferred method)
2. Using BufferedReader
3. Using System.console()
4. Using DataInputStream (Legacy method, not recommended)
// Read an integer
System.out.print("Enter an integer: ");
int num = scanner.nextInt();
// Read a float
System.out.print("Enter a float: ");
float decimal = scanner.nextFloat();
You entered:
Integer: 5
Float: 3.14
Word: Java
Sentence: Java is powerful!
📌 Why use Scanner? ✔ Supports various data types (int, float, double, string, etc.).
✔ Simple syntax and easy to use.
✔ Handles parsing of primitive data types automatically.
46
3. Using BufferedReader (Efficient for Large Inputs)
BufferedReader is faster than Scanner for large input because it reads input as a stream.
🔹 Example: Reading Input Using BufferedReader
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
// Read a string
System.out.print("Enter your name: ");
String name = br.readLine();
// Read an integer
System.out.print("Enter your age: ");
int age = Integer.parseInt(br.readLine());
System.out.println("\nHello, " + name + "! You are " + age + " years old.");
}
}
✅ Output Example
Enter your name: Alice
Enter your age: 25
if (console != null) {
String username = console.readLine("Enter username: ");
char[] password = console.readPassword("Enter password: ");
47
// Clear the password array
java.util.Arrays.fill(password, ' ');
} else {
System.out.println("Console is not available.");
}
}
}
✅ Output Example
Enter username: John
Enter password: (hidden input)
Welcome, John!
📌 Why use System.console()? ✔ Secure (hides password input).
✔ Best for command-line applications.
⚠ Not available in some IDEs (like Eclipse, IntelliJ).
48
7. Best Practices for Console Input
🔹 Use Scanner for small, simple inputs.
🔹 Use BufferedReader for large inputs (performance-efficient).
🔹 Use System.console() for password input (secure).
🔹 Avoid DataInputStream, as it is deprecated.
8. Summary
Method Key Features
Scanner Best for small user inputs, handles multiple data types
BufferedReader Best for large text input, high performance
System.console() Best for secure password input
DataInputStream Deprecated, not recommended
1. Introduction
Java provides several ways to write output to the console, primarily using:
1. System.out.print() & System.out.println()
2. System.out.printf() (for formatted output)
3. System.out.format() (alternative to printf)
4. PrintWriter (for advanced output handling)
8. Summary
Method Key Features
print() Prints without newline
println() Prints with newline
printf() Formatted output (numbers, strings, etc.)
format() Same as printf(), improves readability
PrintWriter Advanced writing, supports auto-flushing
51
public static void main(String[] args) {
File file = new File("example.txt");
if (file.exists()) {
System.out.println("File exists: " + file.getAbsolutePath());
} else {
System.out.println("File does not exist.");
}
}
}
✅ Output (if file exists)
File exists: C:\Users\Documents\example.txt
📌 File Class Methods:
Method Description
exists() Checks if file exists
getName() Returns file name
getAbsolutePath() Returns absolute path
canRead() Checks if file is readable
canWrite() Checks if file is writable
length() Returns file size in bytes
delete() Deletes the file
8. Deleting a File
To delete a file, use delete() from the File class.
54
🔹 Example: Deleting a File
import java.io.File;
55