com.conversantmedia.util.concurrent.PushPullBlockingQueue Maven / Gradle / Ivy
package com.conversantmedia.util.concurrent;
/*
* #%L
* Conversant Disruptor
* ~~
* Conversantmedia.com © 2016, Conversant, Inc. Conversant® is a trademark of Conversant, Inc.
* ~~
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* Single thread implementation of disruptor
*
* @author John Cairns {@literal } Date: 4/25/12 Time: 12:00 PM
*/
public final class PushPullBlockingQueue extends PushPullConcurrentQueue implements Serializable, Iterable, Collection, BlockingQueue, Queue {
// locking objects used for independent locking
// of not empty, not full status, for java BlockingQueue support
// if MultithreadConcurrentQueue is used directly, these calls are
// optimized out and have no impact on timing values
//
protected final Condition queueNotFullCondition;
protected final Condition queueNotEmptyCondition;
/**
*
* Construct a blocking queue of the given fixed capacity.
*
* Note: actual capacity will be the next power of two
* larger than capacity.
*
* @param capacity maximum capacity of this queue
*/
public PushPullBlockingQueue(final int capacity) {
this(capacity, SpinPolicy.WAITING);
}
/**
*
* Construct a blocking queue with a given fixed capacity
*
* Note: actual capacity will be the next power of two
* larger than capacity.
*
* Waiting locking may be used in servers that are tuned for it, waiting
* locking provides a high performance locking implementation which is approximately
* a factor of 2 improvement in throughput (40M/s for 1-1 thread transfers)
*
* However waiting locking is more CPU aggressive and causes servers that may be
* configured with far too many threads to show very high load averages. This is probably
* not as detrimental as it is annoying.
*
* @param capacity - the queue capacity, power of two is suggested
* @param spinPolicy - determine the level of cpu aggressiveness in waiting
*/
public PushPullBlockingQueue(final int capacity, final SpinPolicy spinPolicy) {
super(capacity);
switch(spinPolicy) {
case BLOCKING:
queueNotFullCondition = new QueueNotFull();
queueNotEmptyCondition = new QueueNotEmpty();
break;
case SPINNING:
queueNotFullCondition = new SpinningQueueNotFull();
queueNotEmptyCondition = new SpinningQueueNotEmpty();
break;
case WAITING:
default:
queueNotFullCondition = new WaitingQueueNotFull();
queueNotEmptyCondition = new WaitingQueueNotEmpty();
}
}
/**
*
* Construct a blocking queue of the given fixed capacity
*
* Note: actual capacity will be the next power of two
* larger than capacity.
*
* The values from the collection, c, are appended to the
* queue in iteration order. If the number of elements
* in the collection exceeds the actual capacity, then the
* additional elements overwrite the previous ones until
* all elements have been written once.
*
* @param capacity maximum capacity of this queue
* @param c A collection to use to populate inital values
*/
public PushPullBlockingQueue(final int capacity, Collection extends E> c) {
this(capacity);
for (final E e : c) {
offer(e);
}
}
@Override
public final boolean offer(E e) {
try {
return super.offer(e);
} finally {
queueNotEmptyCondition.signal();
}
}
@Override
public final E poll() {
final E e = super.poll();
// not full now
queueNotFullCondition.signal();
return e;
}
@Override
public int remove(final E[] e) {
final int n = super.remove(e);
// queue can not be full
queueNotFullCondition.signal();
return n;
}
@Override
public E remove() {
return poll();
}
@Override
public E element() {
final E val = peek();
if (val != null)
return val;
throw new NoSuchElementException("No element found.");
}
@Override
public void put(E e) throws InterruptedException {
// add object, wait for space to become available
while (offer(e) == false) {
if(Thread.currentThread().isInterrupted()) {
throw new InterruptedException();
}
queueNotFullCondition.await();
}
}
@Override
public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException {
for (;;) {
if (offer(e)) {
return true;
} else {
// wait for available capacity and try again
if (!Condition.waitStatus(timeout, unit, queueNotFullCondition)) return false;
}
}
}
@Override
public E take() throws InterruptedException {
for (;;) {
E pollObj = poll();
if (pollObj != null) {
return pollObj;
}
if(Thread.currentThread().isInterrupted()) {
throw new InterruptedException();
}
queueNotEmptyCondition.await();
}
}
@Override
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
for(;;) {
E pollObj = poll();
if(pollObj != null) {
return pollObj;
} else {
// wait for the queue to have at least one element or time out
if(!Condition.waitStatus(timeout, unit, queueNotEmptyCondition)) return null;
}
}
}
@Override
public void clear() {
super.clear();
queueNotFullCondition.signal();
}
@Override
public int remainingCapacity() {
return size - size();
}
@Override
public int drainTo(Collection super E> c) {
return drainTo(c, size());
}
@Override
// drain the whole queue at once
public int drainTo(Collection super E> c, int maxElements) {
// required by spec
if (this == c) throw new IllegalArgumentException("Can not drain to self.");
/* This employs a "batch" mechanism to load all objects from the ring
* in a single update. This could have significant cost savings in comparison
* with poll, however it does require a memory allocation.
*/
// save out the values - java should allocate this object on the stack
final E[] pollObj = (E[]) new Object[Math.min(size(), maxElements)];
final int nEle = remove(pollObj);
int nRead = 0;
for (int i = 0; i < nEle; i++) {
if (c.add((E) pollObj[i])) nRead++;
// else invalid state -- object is lost -- see javadoc for drainTo
}
// only return the number that was actually added to the collection
return nRead;
}
@Override
public Object[] toArray() {
final E[] e = (E[]) new Object[size()];
toArray(e);
return e;
}
@Override
public T[] toArray(T[] a) {
remove((E[])a);
return a;
}
@Override
public boolean add(E e) {
if (offer(e)) return true;
throw new IllegalStateException("queue is full");
}
@Override
public boolean remove(Object o) {
throw new UnsupportedOperationException();
}
@Override
public boolean containsAll(Collection> c) {
for (final Object o : c) {
if (!contains(o)) return false;
}
return true;
}
@Override
public boolean addAll(Collection extends E> c) {
boolean rc = false;
for (final E e : c) {
if (offer(e)) {
rc = true;
}
}
return rc;
}
@Override
public boolean removeAll(Collection> c) {
throw new UnsupportedOperationException();
}
@Override
public boolean retainAll(Collection> c) {
throw new UnsupportedOperationException();
}
@Override
public Iterator iterator() {
return new RingIter();
}
private boolean isFull() {
final long queueStart = tail.sum() - size;
return head.sum() == queueStart;
}
private final class RingIter implements Iterator {
int dx = 0;
E lastObj = null;
private RingIter() {
}
@Override
public boolean hasNext() {
return dx < size();
}
@Override
public E next() {
final long pollPos = head.sum();
final int slot = (int) ((pollPos + dx++) & mask);
lastObj = buffer[slot];
return lastObj;
}
@Override
public void remove() {
PushPullBlockingQueue.this.remove(lastObj);
}
}
// condition used for signaling queue is full
private final class QueueNotFull extends AbstractCondition {
@Override
// @return boolean - true if the queue is full
public final boolean test() {
return isFull();
}
}
// condition used for signaling queue is empty
private final class QueueNotEmpty extends AbstractCondition {
@Override
// @return boolean - true if the queue is empty
public final boolean test() {
return isEmpty();
}
}
// condition used for signaling queue is full
private final class WaitingQueueNotFull extends AbstractWaitingCondition {
@Override
// @return boolean - true if the queue is full
public final boolean test() {
return isFull();
}
}
// condition used for signaling queue is empty
private final class WaitingQueueNotEmpty extends AbstractWaitingCondition {
@Override
// @return boolean - true if the queue is empty
public final boolean test() {
return isEmpty();
}
}
// condition used for signaling queue is full
private final class SpinningQueueNotFull extends AbstractSpinningCondition {
@Override
// @return boolean - true if the queue is full
public final boolean test() {
return isFull();
}
}
// condition used for signaling queue is empty
private final class SpinningQueueNotEmpty extends AbstractSpinningCondition {
@Override
// @return boolean - true if the queue is empty
public final boolean test() {
return isEmpty();
}
}
}