org.jgroups.util.ConcurrentLinkedBlockingQueue2 Maven / Gradle / Ivy
package org.jgroups.util;
import java.util.Collection;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Attempt at writing a fast transfer queue, which is bounded. The take() method blocks until there is an element, but
* the offer() method drops the element and returns if the queue is full (doesn't block).
* The design assumes a number of producers but only one consumer. The consumer only blocks when the queue is
* empty (on the not-empty condition), the producers block when the queue is full (on the not-full condition). The
* producers increment a count atomically and if the count is greater than the capacity, they block on the not-full
* condition. The consumer decrements the condition and signals the not-full condition when the count is capacity -1
* (from capacity to capacity-1).
* The producers signal not-empty when the count is 1 (from 0 to 1)
*
* @author Bela Ban
* @since 3.5
*/
public class ConcurrentLinkedBlockingQueue2 extends ConcurrentLinkedQueue implements BlockingQueue {
private static final long serialVersionUID=2539983016900218313L;
protected final int capacity;
protected final AtomicInteger count=new AtomicInteger(0);
// not_empty is signalled by a producer when count went from 0 to 1
protected final Lock not_empty_lock=new ReentrantLock();
protected final Condition not_empty=not_empty_lock.newCondition();
public ConcurrentLinkedBlockingQueue2(int capacity) {
this.capacity=capacity;
Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println("** num_awaits=" + not_empty_awaits)));
/* Thread thread=new Thread() {
public void run() {
while(true) {
System.out.println("*** size=" + size());
Util.sleep(1000);
}
}
};
thread.setDaemon(true);
thread.start();*/
}
int not_empty_awaits=0;
/**
* Drops elements if capacity has been reached. That's OK for the ThreadPoolExecutor as dropped messages
* will get retransmitted
* @param t
* @return
*/
public boolean offer(T t) {
boolean retval=super.offer(t);
if(retval) count.incrementAndGet();
return retval;
}
public T take() throws InterruptedException {
T val=super.poll();
if(val != null) {
decrCount();
return val;
}
waitForNotEmpty();
// at this stage, we are guaranteed to have a value
val=super.poll();
if(val != null)
decrCount();
return val;
}
public T poll() {
T val=super.poll();
if(val != null)
decrCount();
return val;
}
public T poll(long timeout, TimeUnit unit) throws InterruptedException {
return null;
}
public boolean remove(Object o) {
boolean retval=super.remove(o);
if(retval)
decrCount();
return retval;
}
public int remainingCapacity() {
return capacity - size();
}
public int drainTo(Collection super T> c) {
int cnt=0;
if(c == null)
return cnt;
for(;;) {
T el=poll();
if(el == null)
break;
c.add(el);
cnt++;
}
count.set(0);
return cnt;
}
public void put(T t) throws InterruptedException {
if(super.offer(t))
incrCount();
}
public boolean offer(T t, long timeout, TimeUnit unit) throws InterruptedException {
return offer(t);
}
public int size() {
return count.get();
}
public int drainTo(Collection super T> c, int maxElements) {
return drainTo(c);
}
protected void waitForNotEmpty() throws InterruptedException {
while(count.get() == 0) {
not_empty_lock.lock();
try {
// System.out.println("-----> waiting for not empty: num_awaits=" + ++not_empty_awaits + ", count=" + count);
if(count.get() > 0)
return;
not_empty_awaits++;
not_empty.await();
}
finally {
not_empty_lock.unlock();
}
}
}
protected void decrCount() {
count.getAndDecrement();
}
protected void incrCount() {
int prev_count=count.getAndIncrement();
if(prev_count == 0) {
not_empty_lock.lock();
try {
not_empty.signal(); // not signalAll() as there is only *one* consumer !
}
finally {
not_empty_lock.unlock();
}
}
}
}