package com.interview.multithreaded; import java.util.Optional; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; /** * Date 06/25/2015 * @author tusroy * * Write a program to implement bounded blocking queue. This is similar to consumer producer problem * Properties of queue * 1) If queue is empty poll will wait with timeout till item is available * 2) If queue is full offer will wait with timeout till space is available */ public class BoundedBlockingQueue<T> { private final Object[] items; private int takeIndex; private int putIndex; private int count; private final ReentrantLock lock; private final Condition notEmpty; private final Condition notFull; /** * @param size - Define the size of bounded blocking queue. */ public BoundedBlockingQueue(int size){ items = new Object[size]; lock = new ReentrantLock(); notEmpty = lock.newCondition(); notFull = lock.newCondition(); } /** * Poll an item from queue. If queue is empty wait with timeout till item is available * @return Optional<T> depending on if item was polled or queue was empty */ public Optional<T> poll(long timeout, TimeUnit timeUnit) throws InterruptedException{ long left = timeUnit.toNanos(timeout); //acquire the lock on the lock object lock.lockInterruptibly(); T t; try{ //if count is 0 means there is no item to poll. Keep trying to poll //till either item is available or left gets 0 or less which means its //time to time out. while(count == 0){ if(left <= 0){ return Optional.empty(); } //if queue is empty wait fir signal from notEmpty condition left = notEmpty.awaitNanos(timeUnit.toNanos(left)); } //dequeu the item. t = dequeue(); //signal notFull since queue is not full anymore notFull.signal(); } finally { //unlock the lock object lock.unlock(); } return Optional.of(t); } /** * Offer item to queue. If queue is full wait with timeout till space is available. * @param t - item to offer * @param timeout - time out time * @param timeUnit - time out unit * @return - returns true if item was offered in queue successfully else false. * @throws InterruptedException */ public boolean offer(T t, long timeout, TimeUnit timeUnit) throws InterruptedException{ if(t == null) { throw new IllegalArgumentException(); } long left = timeUnit.toNanos(timeout); //acquire lock on lock object lock.lockInterruptibly(); try{ //keep trying if you do not have space available in queue or time out is reached. while(count == items.length){ if(left <= 0){ return false; } left = notFull.awaitNanos(timeUnit.toNanos(left)); } //enqueue the item into the queue enqueue(t); //signal notEmpty condition since queue is not empty anymore notEmpty.signal(); } finally { //release the lock. lock.unlock(); } return true; } private void enqueue(T t){ items[putIndex] = t; if(++putIndex == items.length) { putIndex = 0; } count++; } @SuppressWarnings("unchecked") private T dequeue() { T t = (T)items[takeIndex]; items[takeIndex] = null; if(++takeIndex == items.length) { takeIndex = 0; } count--; return t; } public static void main(String args[]) throws Exception{ verifyQueueWorks(); } public static void verifyQueueWorks() throws Exception{ BoundedBlockingQueue<Integer> queue = new BoundedBlockingQueue<>(30); ExecutorService writeExecutors = Executors.newFixedThreadPool(10); int TOTAL = 10000; AtomicInteger result[] = new AtomicInteger[TOTAL]; AtomicInteger test = new AtomicInteger(0); for(int i = 0; i < TOTAL; i++) { writeExecutors.execute(() -> { try { int val = test.getAndIncrement(); result[val] = new AtomicInteger(1); while(true){ if(queue.offer(val, (long)(Math.random()*100 + 1), TimeUnit.MILLISECONDS)){ break; } } } catch (InterruptedException e) { System.out.println("Shutting down read thread"); } }); } ExecutorService readExecutors = Executors.newFixedThreadPool(10); for(int i = 0; i < TOTAL; i++) { readExecutors.execute(() -> { try { while(true){ Optional<Integer> r = queue.poll((long)(Math.random()*1000 + 1), TimeUnit.MILLISECONDS); if(r.isPresent()) { result[r.get()].incrementAndGet(); } } } catch (InterruptedException e) { System.out.println("Shutting down read thread"); } }); } //you can replace this with countdown latch. But I do not feel like writing all that code. Thread.sleep(10000); System.out.println("Validating result after reasonable wait"); //if queue worked as expected all integers in result array should have value 2. for(int i=0; i < result.length; i++){ if(result[i].get() != 2){ throw new RuntimeException(String.valueOf(i)); } } System.out.println("Shutting down executors"); //force shutdown on read/write executor readExecutors.shutdownNow(); writeExecutors.shutdownNow(); } }