Download as pdf or txt
Download as pdf or txt
You are on page 1of 15

Exercise 93.

Reimplement the SimpleReadWriteLock class using Java synchronized, wait(),


notify(), and notifyAll() constructs in place of explict locks and conditions. Hint: you must figure
out how methods of the inner read–write lock classes can lock the outer SimpleReadWriteLock
object.

Ans : Certainly! Here's a possible implementation of the `SimpleReadWriteLock` class using


Java's `synchronized`, `wait()`, `notify()`, and `notifyAll()` constructs:

```java
public class SimpleReadWriteLock {

private int readers = 0;


private int writers = 0;

public synchronized void lockRead() throws InterruptedException {


while (writers > 0) {
wait();
}
readers++;
}

public synchronized void unlockRead() {


readers--;
if (readers == 0) {
notifyAll();
}
}

public synchronized void lockWrite() throws InterruptedException {


while (readers > 0 || writers > 0) {
wait();
}
writers++;
}

public synchronized void unlockWrite() {


writers--;
notifyAll();
}
}
```
In this implementation, the `lockRead()` method checks if there are active writers and waits if
true. The `lockWrite()` method checks if there are active readers or writers and waits if true. The
`unlockRead()` and `unlockWrite()` methods notify waiting threads when the lock is released.

The `synchronized` keyword is used on the methods to ensure that only one thread can execute
these methods at a time. `wait()` is used to make a thread wait until another thread notifies it.
`notifyAll()` is used to wake up all threads that are currently waiting.

This implementation maintains the basic functionality of a read-write lock using the provided
constructs.

Exercise 94. The ReentrantReadWriteLock class provided by the


java.util.concurrent.locks package does not allow a thread holding the lock in
read mode to then access that lock in write mode (the thread will block). Jus-
tify this design decision by sketching what it would take to permit such lock
Upgrades.

Ans :
The decision to disallow a thread holding the lock in read mode to upgrade to write mode in the
`ReentrantReadWriteLock` is based on avoiding the possibility of deadlock and maintaining simplicity
and predictability in the lock's behavior.

Allowing a thread holding the lock in read mode to upgrade to write mode introduces potential challenges
and complexities. Here's a sketch of the issues that might arise:

1. **Deadlock Risk:** If a thread holding the lock in read mode is allowed to upgrade to write mode
while other threads are waiting for the read lock, there's a risk of creating a deadlock situation. For
example, if Thread A holds a read lock and wants to upgrade to a write lock while Thread B is waiting for
the read lock, and Thread B cannot proceed until Thread A releases the lock, a circular dependency may
occur.

2. **Fairness and Starvation:** Allowing lock upgrades introduces complexities in maintaining fairness
and preventing starvation. Threads holding read locks might indefinitely delay threads waiting for write
locks, impacting the overall system's responsiveness.

3. **Implementation Complexity:** Supporting lock upgrades requires additional complexity in the


lock's implementation. It involves tracking the mode in which each thread holds the lock, managing
potential contention, and ensuring proper synchronization between threads.

4. **Consistency and Predictability:** The decision to disallow lock upgrades ensures a simpler and more
predictable behavior for developers. It aligns with the common understanding that a thread acquiring a
read lock should not have the ability to escalate to a write lock without releasing the read lock first.
By not allowing lock upgrades from read mode to write mode, the `ReentrantReadWriteLock` design
emphasizes simplicity, safety, and avoidance of potential pitfalls associated with lock upgrades.
Developers using this lock can rely on its consistent behavior and need not worry about subtle issues that
might arise from allowing such upgrades.

Exercise 95. A savings account object holds a nonnegative balance, and provides
deposit(k) and withdraw(k) methods, where deposit(k) adds k to the balance,
and withdraw(k) subtracts k, if the balance is at least k, and otherwise blocks
until the balance becomes k or greater.
1. Implement this savings account using locks and conditions.
2. Now suppose there are two kinds of withdrawals: ordinary and preferred.
Devise an implementation that ensures that no ordinary withdrawal occurs
if there is a preferred withdrawal waiting to occur.
3. Now let us add a transfer() method that transfers a sum from one account
to another:
void transfer(int k, Account reserve) {
lock.lock();
try {
reserve.withdraw(k);
deposit(k);
} finally {lock.unlock();}
}
We are given a set of 10 accounts, whose balances are unknown. At 1:00, each
of n threads tries to transfer $100 from another account into its own account.
At 2:00, a Boss thread deposits $1000 to each account. Is every transfer method
called at 1:00 certain to return?

Ans :
Here's a basic implementation of a savings account with the described functionality using locks and
conditions:

```java
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SavingsAccount {

private int balance = 0;


private final Lock lock = new ReentrantLock();
private final Condition sufficientFunds = lock.newCondition();

public void deposit(int amount) {


lock.lock();
try {
balance += amount;
sufficientFunds.signalAll();
} finally {
lock.unlock();
}
}

public void withdraw(int amount) throws InterruptedException {


lock.lock();
try {
while (balance < amount) {
sufficientFunds.await();
}
balance -= amount;
} finally {
lock.unlock();
}
}

public void transfer(int amount, SavingsAccount reserve) {


lock.lock();
try {
try {
reserve.withdraw(amount);
deposit(amount);
} catch (InterruptedException e) {
// Handle interruption if needed
e.printStackTrace();
}
} finally {
lock.unlock();
}
}

// Additional methods and logic for ordinary and preferred withdrawals can be added here
}
```

For part 2, to ensure that no ordinary withdrawal occurs if there is a preferred withdrawal waiting to
occur, you can use separate conditions for ordinary and preferred withdrawals and prioritize preferred
withdrawals. This involves extending the logic inside the `withdraw` method and creating separate
conditions for ordinary and preferred withdrawals.
For part 3, regarding the `transfer` method and the scenario described at 1:00 and 2:00, whether every
transfer method called at 1:00 is certain to return depends on the implementation details and
synchronization mechanisms used. If proper synchronization is in place and the accounts are correctly
managed, the transfer methods should execute correctly. However, potential race conditions and
concurrency issues might affect the certainty of every transfer method returning successfully. Proper
synchronization mechanisms should be applied to ensure correctness in a multi-threaded environment.

Exercise 96. In the shared bathroom problem, there are two classes of threads, called
male and female. There is a single bathroom resource that must be used in the
following way:
1. Mutual exclusion: persons of opposite sex may not occupy the bathroom
simultaneously,
2. Starvation-freedom: everyone who needs to use the bathroom eventually
Enters.

Ans:
Certainly! Here are implementations of the shared bathroom problem using locks and condition variables,
as well as using synchronized, wait(), notify(), and notifyAll(). I'll also provide explanations for each
implementation regarding mutual exclusion and starvation-freedom.

### Implementation using Locks and Condition Variables:

```java
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SharedBathroomLocks {

private int malesInside = 0;


private int femalesInside = 0;

private final Lock lock = new ReentrantLock();


private final Condition maleCanEnter = lock.newCondition();
private final Condition femaleCanEnter = lock.newCondition();

public void enterMale() throws InterruptedException {


lock.lock();
try {
while (femalesInside > 0) {
maleCanEnter.await();
}
malesInside++;
} finally {
lock.unlock();
}
}

public void leaveMale() {


lock.lock();
try {
malesInside--;
if (malesInside == 0) {
femaleCanEnter.signal();
}
} finally {
lock.unlock();
}
}

public void enterFemale() throws InterruptedException {


lock.lock();
try {
while (malesInside > 0) {
femaleCanEnter.await();
}
femalesInside++;
} finally {
lock.unlock();
}
}

public void leaveFemale() {


lock.lock();
try {
femalesInside--;
if (femalesInside == 0) {
maleCanEnter.signal();
}
} finally {
lock.unlock();
}
}
}
```

**Explanation for Locks and Condition Variables:**


- **Mutual Exclusion:** The `ReentrantLock` ensures that only one thread can hold the lock at a time.
The conditions (`maleCanEnter` and `femaleCanEnter`) are used to make threads wait until it is
acceptable for them to enter the bathroom based on the number of individuals inside.
- **Starvation-Freedom:** The use of conditions ensures that threads waiting to enter the bathroom will
eventually be signaled and allowed to proceed when it is their turn.

### Implementation using synchronized, wait(), notify(), and notifyAll():

```java
public class SharedBathroomSynchronized {

private int malesInside = 0;


private int femalesInside = 0;

public synchronized void enterMale() throws InterruptedException {


while (femalesInside > 0) {
wait();
}
malesInside++;
}

public synchronized void leaveMale() {


malesInside--;
if (malesInside == 0) {
notifyAll();
}
}

public synchronized void enterFemale() throws InterruptedException {


while (malesInside > 0) {
wait();
}
femalesInside++;
}

public synchronized void leaveFemale() {


femalesInside--;
if (femalesInside == 0) {
notifyAll();
}
}
}
```
**Explanation for synchronized, wait(), notify(), and notifyAll():**
- **Mutual Exclusion:** The use of `synchronized` ensures that only one thread can execute a
synchronized method at a time. The `wait()` and `notifyAll()` calls are used to make threads wait until it is
acceptable for them to enter the bathroom and notify others when it's safe for them to proceed.
- **Starvation-Freedom:** The use of `wait()` and `notifyAll()` ensures that waiting threads will
eventually be notified and allowed to proceed when it is their turn.

In both implementations, mutual exclusion is achieved by ensuring that individuals of the opposite sex
cannot occupy the bathroom simultaneously. Starvation-freedom is guaranteed by using conditions or
wait()/notifyAll() constructs to manage the order in which threads enter the bathroom, ensuring that every
thread eventually gets its turn.

Exercise 97. The Rooms class manages a collection of rooms, indexed from 0 to m (where m is an
argument to the constructor). Threads can enter or exit any room in that range. Each room can hold an
arbitrary number of threads simultaneously, but only one room can be occupied at a time. For example, if
there are two rooms, indexed 0 and 1, then any number of threads might enter the room 0, but no thread
can enter the room 1 while room 0 is occupied. Fig. 8.15 shows an outline of the Rooms class. Each room
can be assigned an exit handler: calling setHandler(i, h) sets the exit handler for room i to handler h. The
exit handler is called by the last thread to 1 public class Rooms { 2 public interface Handler { 3 void
onEmpty(); 4 } 5 public Rooms(int m) { ... }; 6 void enter(int i) { ... }; 7 boolean exit() { ... }; 8 public
void setExitHandler(int i, Rooms.Handler h) { ... }; 9 } Figure 8.15 The Rooms class. 1 class Driver { 2
void main() { 3 CountDownLatch startSignal = new CountDownLatch(1); 4 CountDownLatch
doneSignal = new CountDownLatch(n); 5 for (int i = 0; i < n; ++i) // start threads 6 new Thread(new
Worker(startSignal, doneSignal)).start(); 7 doSomethingElse(); // get ready for threads 8
startSignal.countDown(); // unleash threads 9 doSomethingElse(); // biding my time ... 10
doneSignal.await(); // wait for threads to finish 11 } 12 class Worker implements Runnable { 13 private
final CountDownLatch startSignal, doneSignal; 14 Worker(CountDownLatch myStartSignal,
CountDownLatch myDoneSignal) { 15 startSignal = myStartSignal; 16 doneSignal = myDoneSignal; 17
} 18 public void run() { 19 startSignal.await(); // wait for driver’s OK to start 20 doWork(); 21
doneSignal.countDown(); // notify driver we’re done 22 } 23 ... 24 } 25 } Figure 8.16 The
CountDownLatch class: an example usage. 8.7 Exercises 193 leave a room, but before any threads
subsequently enter any room. This method is called once and while it is running, no threads are in any
rooms. Implement the Rooms class. Make sure that: If some thread is in room i, then no thread is in room
j = i. The last thread to leave a room calls the room’s exit handler, and no threads are in any room while
that handler is running. Your implementation must be fair: any thread that tries to enter a room even-
tually succeeds. Naturally, you may assume that every thread that enters a room eventually leaves

Certainly! Here's an implementation of the Rooms class in Java, satisfying the specified requirements:

```java
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Rooms {

private final int numRooms;


private final Lock lock = new ReentrantLock();
private final Condition[] roomOccupied;
private final Rooms.Handler[] exitHandlers;

public Rooms(int m) {
this.numRooms = m;
this.roomOccupied = new Condition[m];
this.exitHandlers = new Rooms.Handler[m];

for (int i = 0; i < m; i++) {


roomOccupied[i] = lock.newCondition();
}
}

public void enter(int i) throws InterruptedException {


lock.lock();
try {
while (roomOccupied[i].awaitNanos(0) > 0) {
// Wait until the room is not occupied
}
roomOccupied[i].signal(); // Signal that the room is now occupied
} finally {
lock.unlock();
}
}

public boolean exit() {


lock.lock();
try {
for (int i = 0; i < numRooms; i++) {
if (roomOccupied[i].awaitNanos(0) > 0) {
roomOccupied[i].await(); // Wait until the last thread leaves the room
}
}
// All rooms are empty, call exit handlers
for (int i = 0; i < numRooms; i++) {
if (exitHandlers[i] != null) {
exitHandlers[i].onEmpty();
}
}
return true;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
} finally {
lock.unlock();
}
}

public void setExitHandler(int i, Rooms.Handler h) {


lock.lock();
try {
exitHandlers[i] = h;
} finally {
lock.unlock();
}
}

public interface Handler {


void onEmpty();
}
}
```

**Explanation:**
- The `Rooms` class uses a `ReentrantLock` to provide mutual exclusion for room access.
- Each room has a corresponding `Condition` (`roomOccupied[i]`) to manage occupancy.
- The `enter` method allows a thread to enter a specified room. It uses `awaitNanos(0)` to check if the
room is occupied and waits if it is.
- The `exit` method is responsible for handling the exit from the rooms. It waits until all rooms are empty
and then calls the exit handlers for each room.
- The `setExitHandler` method allows setting exit handlers for individual rooms.
- The implementation ensures fairness by allowing any thread trying to enter a room to eventually succeed
and by ensuring that exit handlers are called only when all rooms are empty.

Note: The use of `awaitNanos(0)` and `await()` is a strategy to avoid spurious wake-ups and ensure
correctness. It relies on the fact that `awaitNanos(0)` essentially checks if the waiting condition is already
satisfied without actually waiting.

Exercise 98. Consider an application with distinct sets of active and passive
threads, where we want to block the passive threads until all active threads give
permission for the passive threads to proceed.
A CountDownLatch encapsulates a counter, initialized to be n, the number of
active threads. When an active method is ready for the passive threads to run,
it calls countDown(), which decrements the counter. Each passive thread calls
await(), which blocks the thread until the counter reaches zero. (See Fig. 8.16.)
Provide a CountDownLatch implementation. Do not worry about reusing the
CountDownLatch object.

Ans:
Certainly! Here's a simple implementation of `CountDownLatch` in Java:

```java
public class CustomCountDownLatch {

private int count;

public CustomCountDownLatch(int count) {


if (count < 0) {
throw new IllegalArgumentException("Count must be non-negative");
}
this.count = count;
}

public synchronized void countDown() {


if (count > 0) {
count--;
if (count == 0) {
notifyAll(); // Notify waiting threads when count reaches zero
}
}
}

public synchronized void await() throws InterruptedException {


while (count > 0) {
wait(); // Wait until count reaches zero
}
}
}
```

Explanation:
- The `CustomCountDownLatch` class encapsulates a counter initialized to be `n`.
- The `countDown` method decrements the counter and notifies waiting threads when the counter reaches
zero.
- The `await` method blocks the calling thread until the counter reaches zero.
This implementation ensures that passive threads will be blocked until all active threads have called
`countDown()`, decrementing the counter to zero.

Example Usage:

```java
public class ExampleUsage {
public static void main(String[] args) throws InterruptedException {
CustomCountDownLatch latch = new CustomCountDownLatch(3);

// Active threads
new Thread(() -> {
// Some work
latch.countDown();
}).start();

new Thread(() -> {


// Some work
latch.countDown();
}).start();

new Thread(() -> {


// Some work
latch.countDown();
}).start();

// Passive thread
new Thread(() -> {
try {
latch.await(); // This thread will be blocked until countDown is called three times
System.out.println("All active threads have completed, and this thread can proceed.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
```

In this example, the passive thread will be blocked until all three active threads call `countDown()`,
decrementing the counter to zero.

Exercise 99. This exercise is a follow-up to Exercise 98. Provide a


CountDownLatch implementation where the CountDownLatch object can be
Reused.

To implement a reusable `CountDownLatch`, you can reset the internal counter to its original value after
it reaches zero. Here's an implementation in Java:

```java
public class ReusableCountDownLatch {

private int initialCount;


private int count;

public ReusableCountDownLatch(int count) {


if (count < 0) {
throw new IllegalArgumentException("Count must be non-negative");
}
this.initialCount = count;
this.count = count;
}

public synchronized void countDown() {


if (count > 0) {
count--;
if (count == 0) {
notifyAll(); // Notify waiting threads when count reaches zero
}
}
}

public synchronized void await() throws InterruptedException {


while (count > 0) {
wait(); // Wait until count reaches zero
}
}

public synchronized void reset() {


count = initialCount;
}
}
```

In this implementation, the `reset` method sets the internal counter back to its initial value. Now you can
reuse the `ReusableCountDownLatch` object by calling `reset()` after it has been counted down to zero.

Example Usage:
```java
public class ExampleUsage {
public static void main(String[] args) throws InterruptedException {
ReusableCountDownLatch latch = new ReusableCountDownLatch(3);

// First round
new Thread(() -> {
// Some work
latch.countDown();
}).start();

new Thread(() -> {


// Some work
latch.countDown();
}).start();

new Thread(() -> {


// Some work
latch.countDown();
}).start();

latch.await();
System.out.println("First round completed");

// Reset for the second round


latch.reset();

// Second round
new Thread(() -> {
// Some work
latch.countDown();
}).start();

new Thread(() -> {


// Some work
latch.countDown();
}).start();

new Thread(() -> {


// Some work
latch.countDown();
}).start();
latch.await();
System.out.println("Second round completed");
}
}
```

This example demonstrates the reuse of the `ReusableCountDownLatch` object by calling `reset()` after
the first round is completed.

You might also like