Java JUC (java.util.concurrent) 7 - Semaphore vs Lock Example
1114. Print in Order
class Foo {
private volatile boolean onePrinted;
private volatile boolean twoPrinted;
public Foo() {
onePrinted = false;
twoPrinted = false;
}
public synchronized void first(Runnable printFirst) throws InterruptedException {
// printFirst.run() outputs "first". Do not change or remove this line.
printFirst.run();
onePrinted = true;
notifyAll();
}
public synchronized void second(Runnable printSecond) throws InterruptedException {
while(!onePrinted) {
wait();
}
// printSecond.run() outputs "second". Do not change or remove this line.
printSecond.run();
twoPrinted = true;
notifyAll();
}
public synchronized void third(Runnable printThird) throws InterruptedException {
while(!twoPrinted) {
wait();
}
// printThird.run() outputs "third". Do not change or remove this line.
printThird.run();
}
}
import java.util.concurrent.*;
class Foo {
Semaphore run2, run3;
public Foo() {
run2 = new Semaphore(0);
run3 = new Semaphore(0);
}
public void first(Runnable printFirst) throws InterruptedException {
printFirst.run();
run2.release();
}
public void second(Runnable printSecond) throws InterruptedException {
run2.acquire();
printSecond.run();
run3.release();
}
public void third(Runnable printThird) throws InterruptedException {
run3.acquire();
printThird.run();
}
}
1188. Design Bounded Blocking Queue
When await() is called, the lock associated with this Condition is atomically released and the current thread becomes disabled for thread scheduling purposes. The current thread is assumed to hold the lock associated with this Condition when this method is called.
newCondition()
await/signal/signalAll
synchonized
wait/notify/notifyAll
The ReentrantLock is an exclusive lock so only one thread can acquire the lock.
See #1. When a thread call signal or signalAll, it releases respectively one thread or all threads awaiting for the corresponding Condition such that the thread or those threads will be eligible to acquire the lock again. But for now the lock is still owned by the thread that called signal or signalAll until it releases explicitly the lock by calling lock.unlock(). Then the thread(s) that has/have been released will be able to try to acquire the lock again, the thread that could acquire the lock will be able to check the condition again (by condition this time I mean count == items.length or count == 0 in this example), if it is ok it will proceed otherwise it will await again and release the lock to make it available to another thread.
class BoundedBlockingQueue {
private Queue<Integer> queue;
private Semaphore consumerSemaphore;
private Semaphore producerSemaphore;
private Semaphore mutex;
public BoundedBlockingQueue(int capacity) {
this.queue = new LinkedList<>();
this.producerSemaphore = new Semaphore(capacity);
this.consumerSemaphore = new Semaphore(0);
this.mutex = new Semaphore(1);
}
public void enqueue(int element) throws InterruptedException {
this.producerSemaphore.acquire();
//use mutex with Linkedlist or use ConcurrentLinkedDeque<Integer> q without mutex
this.mutex.acquire();
this.queue.offer(element);
this.mutex.release();
this.consumerSemaphore.release();
}
public int dequeue() throws InterruptedException {
this.consumerSemaphore.acquire();
this.mutex.acquire();
Integer res = this.queue.poll();
this.mutex.release();
this.producerSemaphore.release();
return res == null ? -1 : res;
}
public int size() {
return this.queue.size();
}
}
class BoundedBlockingQueue {
private Queue<Integer> queue;
private int capacity;
public BoundedBlockingQueue(int capacity) {
this.capacity = capacity;
this.queue = new LinkedList<>();
}
public void enqueue(int element) throws InterruptedException {
//object level synchronized so either enqueu or dequeue can run
synchronized(this) {
while (this.queue.size() == this.capacity) {
wait();
}
this.queue.offer(element);
notifyAll();
}
}
public int dequeue() throws InterruptedException {
synchronized(this) {
while (this.queue.isEmpty()) {
wait();
}
int res = this.queue.poll();
notifyAll();
return res;
}
}
public int size() {
return this.queue.size();
}
}
class BoundedBlockingQueue {
private int[] data;
private int capacity = 0, head = 0, tail = 0;
private volatile int size = 0;
private Lock lock = new ReentrantLock();
private Condition isFull = lock.newCondition(), isEmpty = lock.newCondition();
public BoundedBlockingQueue(int capacity) {
this.data = new int[capacity];
this.capacity = capacity;
}
public void enqueue(int element) throws InterruptedException {
try {
lock.lock();
while (size == capacity) {
isFull.await(); // only one thread will get lock after all threads wake up by signalAll().
}
data[tail++ % capacity] = element;
size++;
isEmpty.signalAll();
} catch(InterruptedException e) {
} finally {
lock.unlock();
}
}
public int dequeue() throws InterruptedException {
int res = 0;
try {
lock.lock();
while (size == 0) {
isEmpty.await();
}
isFull.signalAll();
res = data[head++ % capacity];
size--;
} catch(InterruptedException e) {
} finally {
lock.unlock();
}
return res;
}
public int size() {
return size;
}
}
In order for the two methods to run concurrently they would have to use different locks
class A {
private final Object lockA = new Object();
private final Object lockB = new Object();
public void methodA() {
synchronized(lockA) {
//method A
}
}
public void methodB() {
synchronized(lockB) {
//method B
}
}
}