Skip to content

Commit 30ef96d

Browse files
committed
Two-Hundred-Ninety-Nine Commit: Amend Concurrency_Tutorial.txt in Concurrency section
1 parent 151ceaa commit 30ef96d

File tree

1 file changed

+154
-140
lines changed

1 file changed

+154
-140
lines changed

src/Concurrency/Concurrency_Tutorial.txt

Lines changed: 154 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -33,106 +33,111 @@ Concurrency
3333
=====================================
3434

3535
* Problem Statement:
36+
--------------------
37+
3638
Implement a thread-safe bounded blocking queue using Java that supports the operations `enqueue` and `dequeue`.
3739
The queue should block the enqueuing thread if the queue is full and block the dequeuing thread if the queue is empty.
3840

3941
* Pseudocode:
42+
-------------
43+
44+
class BoundedBlockingQueue {
45+
private Queue<Integer> queue;
46+
private int capacity;
47+
private Lock lock;
48+
private Condition notFull;
49+
private Condition notEmpty;
50+
51+
BoundedBlockingQueue(int capacity) {
52+
this.queue = new LinkedList<>();
53+
this.capacity = capacity;
54+
this.lock = new ReentrantLock();
55+
this.notFull = lock.newCondition();
56+
this.notEmpty = lock.newCondition();
57+
}
4058

41-
class BoundedBlockingQueue {
42-
private Queue<Integer> queue;
43-
private int capacity;
44-
private Lock lock;
45-
private Condition notFull;
46-
private Condition notEmpty;
47-
48-
BoundedBlockingQueue(int capacity) {
49-
this.queue = new LinkedList<>();
50-
this.capacity = capacity;
51-
this.lock = new ReentrantLock();
52-
this.notFull = lock.newCondition();
53-
this.notEmpty = lock.newCondition();
54-
}
55-
56-
void enqueue(int item) {
57-
lock.lock();
58-
try {
59-
while (queue.size() == capacity) {
60-
notFull.await();
59+
void enqueue(int item) {
60+
lock.lock();
61+
try {
62+
while (queue.size() == capacity) {
63+
notFull.await();
64+
}
65+
queue.add(item);
66+
notEmpty.signalAll();
67+
} finally {
68+
lock.unlock();
6169
}
62-
queue.add(item);
63-
notEmpty.signalAll();
64-
} finally {
65-
lock.unlock();
6670
}
67-
}
6871

69-
int dequeue() {
70-
lock.lock();
71-
try {
72-
while (queue.isEmpty()) {
73-
notEmpty.await();
72+
int dequeue() {
73+
lock.lock();
74+
try {
75+
while (queue.isEmpty()) {
76+
notEmpty.await();
77+
}
78+
int item = queue.remove();
79+
notFull.signalAll();
80+
return item;
81+
} finally {
82+
lock.unlock();
7483
}
75-
int item = queue.remove();
76-
notFull.signalAll();
77-
return item;
78-
} finally {
79-
lock.unlock();
8084
}
8185
}
82-
}
8386

8487
* Java Code:
88+
------------
89+
90+
import java.util.LinkedList;
91+
import java.util.Queue;
92+
import java.util.concurrent.locks.Condition;
93+
import java.util.concurrent.locks.Lock;
94+
import java.util.concurrent.locks.ReentrantLock;
95+
96+
public class BoundedBlockingQueue {
97+
private Queue<Integer> queue;
98+
private int capacity;
99+
private Lock lock;
100+
private Condition notFull;
101+
private Condition notEmpty;
102+
103+
public BoundedBlockingQueue(int capacity) {
104+
this.queue = new LinkedList<>();
105+
this.capacity = capacity;
106+
this.lock = new ReentrantLock();
107+
this.notFull = lock.newCondition();
108+
this.notEmpty = lock.newCondition();
109+
}
85110

86-
import java.util.LinkedList;
87-
import java.util.Queue;
88-
import java.util.concurrent.locks.Condition;
89-
import java.util.concurrent.locks.Lock;
90-
import java.util.concurrent.locks.ReentrantLock;
91-
92-
public class BoundedBlockingQueue {
93-
private Queue<Integer> queue;
94-
private int capacity;
95-
private Lock lock;
96-
private Condition notFull;
97-
private Condition notEmpty;
98-
99-
public BoundedBlockingQueue(int capacity) {
100-
this.queue = new LinkedList<>();
101-
this.capacity = capacity;
102-
this.lock = new ReentrantLock();
103-
this.notFull = lock.newCondition();
104-
this.notEmpty = lock.newCondition();
105-
}
106-
107-
public void enqueue(int item) throws InterruptedException {
108-
lock.lock();
109-
try {
110-
while (queue.size() == capacity) {
111-
notFull.await();
111+
public void enqueue(int item) throws InterruptedException {
112+
lock.lock();
113+
try {
114+
while (queue.size() == capacity) {
115+
notFull.await();
116+
}
117+
queue.add(item);
118+
notEmpty.signalAll();
119+
} finally {
120+
lock.unlock();
112121
}
113-
queue.add(item);
114-
notEmpty.signalAll();
115-
} finally {
116-
lock.unlock();
117122
}
118-
}
119123

120-
public int dequeue() throws InterruptedException {
121-
lock.lock();
122-
try {
123-
while (queue.isEmpty()) {
124-
notEmpty.await();
124+
public int dequeue() throws InterruptedException {
125+
lock.lock();
126+
try {
127+
while (queue.isEmpty()) {
128+
notEmpty.await();
129+
}
130+
int item = queue.remove();
131+
notFull.signalAll();
132+
return item;
133+
} finally {
134+
lock.unlock();
125135
}
126-
int item = queue.remove();
127-
notFull.signalAll();
128-
return item;
129-
} finally {
130-
lock.unlock();
131136
}
132137
}
133-
}
134138

135139
* Time and Space Complexity:
140+
----------------------------
136141

137142
Time Complexity:
138143
- `enqueue()`: O(1) - Adding an element to the queue is a constant-time operation.
@@ -142,7 +147,8 @@ Concurrency
142147
- The space complexity is O(n) where n is the capacity of the queue. The queue stores up to `n` elements, and
143148
additional space is used for locks and condition variables.
144149

145-
* Considerations for Concurrency in Coding Interviews
150+
* Considerations for Concurrency in Coding Interviews:
151+
-------------------------------------------------------
146152

147153
1. Thread Safety: Ensure your code handles concurrent access correctly, avoiding race conditions and deadlocks.
148154
2. Synchronization: Use appropriate synchronization primitives to manage access to shared resources. In Java, this
@@ -158,86 +164,94 @@ Concurrency
158164
The Dining Philosophers problem is a classic example of synchronization issues in concurrent programming. To solve this problem,
159165
we need to ensure that no philosopher starves and that the system avoids deadlocks (ChatGPT coded the solution 🤖).
160166

161-
* Solution Explanation
167+
* Solution Explanation:
168+
-----------------------
162169

163170
One common approach to solve this problem is to enforce an ordering on the acquisition of forks to prevent circular wait conditions
164171
that could lead to a deadlock. Here, we can impose an order by making each philosopher pick up the lower-numbered fork first, and
165172
then the higher-numbered fork. This ensures that there is no circular wait.
166173

167-
* Java Implementation
174+
* Java Implementation:
175+
----------------------
168176

169177
Here's the Java implementation of the Dining Philosophers problem using the above strategy:
170178

171-
import java.util.concurrent.locks.Lock;
172-
import java.util.concurrent.locks.ReentrantLock;
179+
import java.util.concurrent.locks.Lock;
180+
import java.util.concurrent.locks.ReentrantLock;
173181

174-
public class DiningPhilosophers {
182+
public class DiningPhilosophers {
175183

176-
private final Lock[] forks = new ReentrantLock[5];
184+
private final Lock[] forks = new ReentrantLock[5];
177185

178-
public DiningPhilosophers() {
179-
for (int i = 0; i < 5; i++) {
180-
forks[i] = new ReentrantLock();
186+
public DiningPhilosophers() {
187+
for (int i = 0; i < 5; i++) {
188+
forks[i] = new ReentrantLock();
189+
}
181190
}
182-
}
183191

184-
public void wantsToEat(int philosopher,
185-
Runnable pickLeftFork,
186-
Runnable pickRightFork,
187-
Runnable eat,
188-
Runnable putLeftFork,
189-
Runnable putRightFork) throws InterruptedException {
190-
int leftFork = philosopher;
191-
int rightFork = (philosopher + 1) % 5;
192-
193-
// Pick forks in a globally consistent order to avoid deadlocks
194-
if (philosopher % 2 == 0) {
195-
forks[leftFork].lock();
196-
pickLeftFork.run();
197-
forks[rightFork].lock();
198-
pickRightFork.run();
199-
} else {
200-
forks[rightFork].lock();
201-
pickRightFork.run();
202-
forks[leftFork].lock();
203-
pickLeftFork.run();
204-
}
192+
public void wantsToEat(int philosopher,
193+
Runnable pickLeftFork,
194+
Runnable pickRightFork,
195+
Runnable eat,
196+
Runnable putLeftFork,
197+
Runnable putRightFork) throws InterruptedException {
198+
int leftFork = philosopher;
199+
int rightFork = (philosopher + 1) % 5;
200+
201+
// Pick forks in a globally consistent order to avoid deadlocks
202+
if (philosopher % 2 == 0) {
203+
forks[leftFork].lock();
204+
pickLeftFork.run();
205+
forks[rightFork].lock();
206+
pickRightFork.run();
207+
} else {
208+
forks[rightFork].lock();
209+
pickRightFork.run();
210+
forks[leftFork].lock();
211+
pickLeftFork.run();
212+
}
205213

206-
// Eat
207-
eat.run();
208-
209-
// Put down forks in the reverse order of picking up
210-
if (philosopher % 2 == 0) {
211-
putRightFork.run();
212-
forks[rightFork].unlock();
213-
putLeftFork.run();
214-
forks[leftFork].unlock();
215-
} else {
216-
putLeftFork.run();
217-
forks[leftFork].unlock();
218-
putRightFork.run();
219-
forks[rightFork].unlock();
214+
// Eat
215+
eat.run();
216+
217+
// Put down forks in the reverse order of picking up
218+
if (philosopher % 2 == 0) {
219+
putRightFork.run();
220+
forks[rightFork].unlock();
221+
putLeftFork.run();
222+
forks[leftFork].unlock();
223+
} else {
224+
putLeftFork.run();
225+
forks[leftFork].unlock();
226+
putRightFork.run();
227+
forks[rightFork].unlock();
228+
}
220229
}
221230
}
222-
}
223-
```
224231

225-
* Explanation
232+
* Explanation:
233+
--------------
226234

227-
1. Lock Array: We use an array of `ReentrantLock` objects to represent the forks. Each fork is shared between two adjacent philosophers.
228-
2. Consistent Order: Philosophers pick up forks in a consistent order based on their ID to avoid deadlock:
229-
- Even-numbered philosophers pick up the left fork first and then the right fork.
230-
- Odd-numbered philosophers pick up the right fork first and then the left fork.
231-
3. Locking: The `lock` method ensures that a philosopher can only proceed if both forks are available. This prevents any race conditions.
232-
4. Unlocking: After eating, philosophers put down the forks in the reverse order they were picked up to maintain consistency and release
233-
the resources properly.
235+
1. Lock Array: We use an array of `ReentrantLock` objects to represent the forks. Each fork is shared between
236+
two adjacent philosophers.
237+
2. Consistent Order: Philosophers pick up forks in a consistent order based on their ID to avoid deadlock:
238+
- Even-numbered philosophers pick up the left fork first and then the right fork.
239+
- Odd-numbered philosophers pick up the right fork first and then the left fork.
240+
3. Locking: The `lock` method ensures that a philosopher can only proceed if both forks are available. This
241+
prevents any race conditions.
242+
4. Unlocking: After eating, philosophers put down the forks in the reverse order they were picked up to maintain
243+
consistency and release the resources properly.
234244

235-
* Time and Space Complexity
245+
* Time and Space Complexity:
246+
----------------------------
236247

237-
Time Complexity:
238-
- The time complexity for each philosopher to pick up and put down the forks is O(1) because locking and unlocking are constant-time operations.
248+
Time Complexity:
249+
- The time complexity for each philosopher to pick up and put down the forks is O(1) because locking and unlocking
250+
are constant-time operations.
239251

240-
Space Complexity:
241-
- The space complexity is O(1) for each philosopher since we only store references to the forks and use a constant amount of extra space (five locks).
252+
Space Complexity:
253+
- The space complexity is O(1) for each philosopher since we only store references to the forks and use a constant
254+
amount of extra space (five locks).
242255

243-
This solution ensures that no philosopher will starve and that the system avoids deadlocks, fulfilling the requirements of the problem.
256+
This solution ensures that no philosopher will starve and that the system avoids deadlocks, fulfilling the requirements
257+
of the problem.

0 commit comments

Comments
 (0)