This solution has a total of four classes.
Main.java: Class withmainmethod. Execution starts here.BoundedBufferV2.java: The class representing a thread-safe queue of limited capacity.ProducerThreadV2.java: A class representing the producer thread. It produces a random string and tries to push it into the queue. Waits for a second before doing the same thing again.ConsumerThreadV2.java: A class representing the consumer thread. It tries to get an item from the queue. Waits for a second before doing the same thing again.
Main.java
public class Main {
public static void main(String[] args) {
BoundedBufferV2 boundedBufferV2 = new BoundedBufferV2(10);
for (int i = 0; i < 5; i++) {
new ProducerThreadV2(boundedBufferV2).start();
}
for (int i = 0; i < 5; i++) {
new ConsumerThreadV2(boundedBufferV2).start();
}
}
}
BoundedBufferV2.java - This is the main class where the thread-safety is implemented.
public class BoundedBufferV2 {
private final Queue<String> queue;
private final Lock lock;
private final Semaphore full;
private final Semaphore empty;
public BoundedBufferV2(int capacity) {
this.queue = new ArrayDeque<>(capacity);
this.lock = new ReentrantLock();
this.full = new Semaphore(0, true);
this.empty = new Semaphore(capacity, true);
}
public void enqueue(String item) {
boolean hasSpace = empty.tryAcquire();
if (!hasSpace) return;
boolean lockAcquired = lock.tryLock();
if (!lockAcquired) {
empty.release();
return;
}
try {
queue.add(item);
System.out.println("ENQ-> " + queue);
} finally {
lock.unlock();
full.release();
}
}
public String dequeue() {
boolean hasItems = full.tryAcquire();
if (!hasItems) return null;
boolean lockAcquired = lock.tryLock();
if (!lockAcquired) {
full.release();
return null;
}
try {
String item = queue.poll();
System.out.println("DEQ-> " + queue);
return item;
} finally {
lock.unlock();
empty.release();
}
}
}
ProducerThreadV2.java
import java.util.Random;
public class ProducerThreadV2 extends Thread {
private final Random random;
private final BoundedBufferV2 boundedBufferV2;
public ProducerThreadV2(BoundedBufferV2 boundedBufferV2) {
this.random = new Random();
this.boundedBufferV2 = boundedBufferV2;
}
@Override
public void run() {
while (true) {
String randomStr = String.valueOf(random.nextInt());
boundedBufferV2.enqueue(randomStr);
try {
Thread.sleep(1_000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
ConsumerThreadV2.java
public class ConsumerThreadV2 extends Thread {
private final BoundedBufferV2 boundedBufferV2;
public ConsumerThreadV2(BoundedBufferV2 boundedBufferV2) {
this.boundedBufferV2 = boundedBufferV2;
}
@Override
public void run() {
while (true) {
boundedBufferV2.dequeue();
try {
Thread.sleep(1_000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
A couple of problems I already realize are busy-waiting if Semaphore acquisition or Lock acquisition does not happen and returning null from the method. I am looking for inputs on Deadlock, race-condition or data race. Thank you.