Assignment 5
Note: Attempt any two questions from the following [5 Marks Each]
Q1: Explain the difference between the Thread class and the Runnable interface in Java.
Provide examples of situations where you might choose one approach over the other.
SOLUTION : In Java, `Thread` and `Runnable` are two ways to create and manage
threads. Here's the difference between them:
# Thread Class:
- A `Thread` is a class that represents a separate ow of execution in a program.
- To create a new thread using the `Thread` class, you need to extend the `Thread` class
and override the `run()` method.
- The `Thread` class provides methods to control the thread's execution, such as `start()`,
`join()`, and `interrupt()`.
# Runnable Interface:
- A `Runnable` is an interface that represents a task that can be executed by a thread.
- To create a new thread using the `Runnable` interface, you need to implement the
`Runnable` interface and provide an implementation for the `run()` method.
- A `Runnable` object can be passed to a `Thread` object, which will execute the `run()`
method.
# Key Differences:
1. *Inheritance*: When you extend the `Thread` class, you can't extend any other class.
When you implement the `Runnable` interface, you can still extend other classes.
2. *Flexibility*: The `Runnable` interface provides more exibility, as you can pass a
`Runnable` object to different types of executors or threads.
3. *Reusability*: A `Runnable` object can be reused in different threads, whereas a
`Thread` object is tightly coupled to its execution.
# Choosing Between Thread and Runnable:
1. *Use Thread*: When you need to control the thread's execution directly, such as when
you need to use thread-speci c methods like `join()` or `interrupt()`.
2. *Use Runnable*: When you want to decouple the task from the thread execution, or
when you need to pass the task to different types of executors or threads.
# Examples:
*Thread Example:*
```
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("Hello from thread!");
}
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
```
fi
fl
fl
Assignment 5
*Runnable Example:*
```
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Hello from runnable!");
}
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
}
}
```
Q2: Write a Java program that demonstrates the creation of a thread by extending the
Thread class. Include a simple task for the thread to execute.
SOLUTION: Here's a Java program that demonstrates the creation of a thread by
extending the `Thread` class:
# Creating a Thread by Extending Thread Class:
```
// De ne a class that extends Thread
class MyThread extends Thread {
private String threadName;
public MyThread(String threadName) {
this.threadName = threadName;
}
@Override
public void run() {
// Task to be executed by the thread
System.out.println(threadName + " is starting.");
for (int i = 1; i <= 5; i++) {
System.out.println(threadName + " is running iteration " + i);
try {
Thread.sleep(1000); // Sleep for 1 second
} catch (InterruptedException e) {
System.out.println(threadName + " was interrupted.");
}
}
System.out.println(threadName + " has nished.");
}
}
public class Main {
fi
fi
Assignment 5
public static void main(String[] args) {
// Create two threads
MyThread thread1 = new MyThread("Thread-1");
MyThread thread2 = new MyThread("Thread-2");
// Start the threads
thread1.start();
thread2.start();
}
}
```
# Explanation:
1. *De ne a class that extends Thread*: We de ne a class `MyThread` that extends the
`Thread` class.
2. *Override the run() method*: We override the `run()` method to de ne the task that the
thread will execute.
3. *Create and start threads*: In the `main()` method, we create two instances of
`MyThread` and start them using the `start()` method.
# Output:
The output will show both threads running concurrently, with each thread printing its name
and iteration number. The exact output may vary due to the concurrent nature of thread
execution.
# Key Points:
1. *Thread creation*: Threads are created by extending the `Thread` class and overriding
the `run()` method.
2. *Thread execution*: Threads are executed by calling the `start()` method, which calls
the `run()` method internally.
3. *Concurrency*: Multiple threads can run concurrently, improving responsiveness and
system utilization.
Note: Attempt any two questions from the following [10 Marks Each]
Q4: Implement a Java program that shows the use of synchroniSation to avoid race
conditions. Provide a brief explanation of how synchronisation is achieved in your program
SOLUTION : Here's a Java program that demonstrates the use of synchronization to avoid
race conditions:
# Synchronized Program:
```
class Counter {
private int count = 0;
// Synchronized method to increment count
public synchronized void increment() {
count++;
fi
fi
fi
Assignment 5
}
// Synchronized method to get count
public synchronized int getCount() {
return count;
}
}
class CounterThread extends Thread {
private Counter counter;
public CounterThread(Counter counter) {
this.counter = counter;
}
@Override
public void run() {
for (int i = 0; i < 100000; i++) {
counter.increment();
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
// Create two threads
CounterThread thread1 = new CounterThread(counter);
CounterThread thread2 = new CounterThread(counter);
// Start the threads
thread1.start();
thread2.start();
// Wait for both threads to nish
thread1.join();
thread2.join();
// Print the nal count
System.out.println("Final count: " + counter.getCount());
}
}
```
# Explanation:
1. *Synchronized methods*: We declare the `increment()` and `getCount()` methods in the
`Counter` class as `synchronized`. This ensures that only one thread can execute these
methods at a time.
2. *Mutex lock*: When a thread calls a `synchronized` method, it acquires a mutex lock on
the object. Other threads cannot call `synchronized` methods on the same object until the
lock is released.
fi
fi
Assignment 5
3. *Thread safety*: By synchronizing access to the `count` variable, we ensure that the
increment operation is atomic and thread-safe.
# Bene ts:
1. *Prevents race conditions*: Synchronization prevents multiple threads from accessing
shared data simultaneously, avoiding race conditions.
2. *Ensures data consistency*: Synchronization ensures that shared data is consistent and
accurate.
# Key Points:
1. *Use synchronized keyword*: Use the `synchronized` keyword to declare methods or
blocks that require exclusive access to shared data.
2. *Locking mechanism*: Synchronization uses a locking mechanism to ensure that only
one thread can access shared data at a time.
3. *Thread safety*: Synchronization is essential for ensuring thread safety in multithreaded
programs.
Q5: Discuss the advantages of using the HashMap class in Java's Collections Framework.
Provide examples of situations where a HashMap is more suitable than other map
implementations.
SOLUTION : The `HashMap` class in Java's Collections Framework offers several
advantages:
# Advantages of HashMap:
1. *Fast lookup and insertion*: `HashMap` provides fast lookup, insertion, and deletion
operations with an average time complexity of O(1), making it suitable for large datasets.
2. *Ef cient storage*: `HashMap` stores key-value pairs ef ciently, using a hash table data
structure that minimizes memory usage.
3. *Flexible key and value types*: `HashMap` allows any non-null object as a key and any
object as a value, providing exibility in data storage and retrieval.
# Situations where HashMap is more suitable:
1. *Caching*: `HashMap` is suitable for caching frequently accessed data, as it provides
fast lookup and insertion operations.
2. *Data aggregation*: `HashMap` can be used to aggregate data from various sources,
such as counting occurrences of words in a text or aggregating data by category.
3. *Con guration storage*: `HashMap` can be used to store con guration data, such as
user preferences or application settings.
# Comparison with other map implementations:
1. *TreeMap*: `TreeMap` provides a sorted map implementation, which is useful when
data needs to be ordered or sorted. However, `TreeMap` has a slower lookup time
complexity (O(log n)) compared to `HashMap`.
2. *LinkedHashMap*: `LinkedHashMap` provides a map implementation that preserves the
order of insertion, which is useful when data needs to be processed in a speci c order.
However, `LinkedHashMap` may have slower performance compared to `HashMap` due to
the additional overhead of maintaining the linked list.
# Example usage:
```
fi
fi
fi
fl
fi
fi
fi
Assignment 5
import java.util.HashMap;
import java.util.Map;
public class HashMapExample {
public static void main(String[] args) {
// Create a HashMap
Map<String, Integer> studentGrades = new HashMap<>();
// Insert data into the HashMap
studentGrades.put("John", 85);
studentGrades.put("Alice", 90);
studentGrades.put("Bob", 78);
// Retrieve data from the HashMap
System.out.println("John's grade: " + studentGrades.get("John"));
// Update data in the HashMap
studentGrades.put("John", 88);
System.out.println("John's updated grade: " + studentGrades.get("John"));
}
}
```
Note: Attempt any two questions from the following [20 Marks Each]
Q7: Design and implement a multithreaded Java program that demonstrates thread
communication using wait(), notify(), or notifyAll(). Provide a detailed explanation of how the
threads communicate
SOLUTION: Here's a Java program that demonstrates thread communication using `wait()`,
`notify()`, and `notifyAll()`:
# Producer-Consumer Problem:
```
import java.util.LinkedList;
import java.util.Queue;
class SharedQueue {
private Queue<Integer> queue = new LinkedList<>();
private nal int MAX_SIZE = 5;
public synchronized void produce(int item) throws InterruptedException {
while (queue.size() == MAX_SIZE) {
System.out.println("Queue is full. Producer is waiting.");
wait();
}
queue.add(item);
System.out.println("Produced: " + item);
notifyAll(); // Notify consumers that an item is available
}
public synchronized int consume() throws InterruptedException {
fi
Assignment 5
while (queue.isEmpty()) {
System.out.println("Queue is empty. Consumer is waiting.");
wait();
}
int item = queue.poll();
System.out.println("Consumed: " + item);
notifyAll(); // Notify producers that space is available
return item;
}
}
class Producer extends Thread {
private SharedQueue queue;
public Producer(SharedQueue queue) {
this.queue = queue;
}
@Override
public void run() {
try {
for (int i = 1; i <= 10; i++) {
queue.produce(i);
Thread.sleep(100); // Simulate production time
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
class Consumer extends Thread {
private SharedQueue queue;
public Consumer(SharedQueue queue) {
this.queue = queue;
}
@Override
public void run() {
try {
for (int i = 1; i <= 10; i++) {
queue.consume();
Thread.sleep(100); // Simulate consumption time
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public class Main {
public static void main(String[] args) {
SharedQueue queue = new SharedQueue();
Producer producer = new Producer(queue);
Consumer consumer = new Consumer(queue);
Assignment 5
producer.start();
consumer.start();
}
}
```
# Explanation:
1. *SharedQueue class*: This class represents a shared queue that producers and consumers
access. It uses `wait()` and `notifyAll()` to coordinate access to the queue.
2. *Producer thread*: The producer thread produces items and adds them to the queue. If the
queue is full, it waits until space becomes available.
3. *Consumer thread*: The consumer thread consumes items from the queue. If the queue is
empty, it waits until an item becomes available.
4. *wait() and notifyAll()*: These methods are used to coordinate access to the queue. When a
producer or consumer thread is waiting, it releases the lock on the queue, allowing other threads to
access it. When an item is added or removed from the queue, the `notifyAll()` method is called to
wake up waiting threads.
# Thread Communication:
1. *Producer-consumer synchronization*: The producer and consumer threads synchronize access
to the shared queue using `wait()` and `notifyAll()`.
2. *Queue full or empty*: When the queue is full or empty, the producer or consumer thread waits
until the condition changes.
3. *Noti cation*: When an item is added or removed from the queue, the `notifyAll()` method is
called to wake up waiting threads.
Q8: Explore the concepts of deadlocks in multithreading. Explain the key characteristics of
deadlocks and provide strategies for deadlock prevention and handling. Include examples to
support your explanations.
SOLUTION: Deadlocks are a fundamental problem in multithreading that can cause a program to
freeze or become unresponsive. Here's an explanation of deadlocks, their characteristics, and
strategies for prevention and handling:
# What is a Deadlock?
A deadlock is a situation where two or more threads are blocked inde nitely, each waiting for the
other to release a resource. This creates a cycle of dependency, where none of the threads can
proceed.
# Key Characteristics of Deadlocks:
1. *Mutual Exclusion*: Two or more threads require exclusive access to a common resource.
2. *Hold and Wait*: One thread holds a resource and waits for another resource held by another
thread.
3. *No Preemption*: The operating system does not force a thread to release a resource.
4. *Circular Wait*: A cycle of threads exists, where each thread is waiting for a resource held by
another thread.
# Strategies for Deadlock Prevention:
1. *Avoid Nested Locks*: Minimize the use of nested locks, which can lead to deadlocks.
2. *Lock Ordering*: Establish a consistent lock ordering to prevent deadlocks.
3. *Avoid Unnecessary Locks*: Only lock resources when necessary, and release locks as soon as
possible.
4. *Use Lock Timeout*: Use lock timeout mechanisms to detect and recover from deadlocks.
fi
fi
Assignment 5
# Strategies for Deadlock Handling:
1. *Deadlock Detection*: Implement deadlock detection mechanisms to identify deadlocks.
2. *Deadlock Recovery*: Develop strategies for recovering from deadlocks, such as aborting one of
the threads.
3. *Thread Priorities*: Assign priorities to threads to ensure that critical threads are not blocked.
# Example:
```
public class DeadlockExample {
private static nal Object lock1 = new Object();
private static nal Object lock2 = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("Thread 1: Holding lock 1...");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("Thread 1: Waiting for lock 2...");
synchronized (lock2) {
System.out.println("Thread 1: Holding lock 1 and lock 2...");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock2) {
System.out.println("Thread 2: Holding lock 2...");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("Thread 2: Waiting for lock 1...");
synchronized (lock1) {
System.out.println("Thread 2: Holding lock 1 and lock 2...");
}
}
});
thread1.start();
thread2.start();
}
}
```
fi
fi