/* * Copyright (c) 2011-2016 Pivotal Software Inc, All Rights Reserved. * * 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. */ package reactor.util.concurrent; import java.util.AbstractQueue; import java.util.Iterator; import java.util.Objects; import java.util.concurrent.atomic.AtomicLongFieldUpdater; import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.function.BiPredicate; /** * An unbounded, array-backed single-producer, single-consumer queue with a fixed link * size. * <p> * This implementation is based on JCTools' SPSC algorithms: <a * href='https://github.com/JCTools/JCTools/blob/master/jctools-core/src/main/java/org/jctools/queues/SpscUnboundedArrayQueue.java'>SpscUnboundedArrayQueue</a> * and <a href='https://github.com/JCTools/JCTools/blob/master/jctools-core/src/main/java/org/jctools/queues/atomic/SpscUnboundedAtomicArrayQueue.java'>SpscUnboundedAtomicArrayQueue</a> * of which the {@code SpscUnboundedAtomicArrayQueue} was contributed by one of the * authors of this library. The notable difference is that this class is not padded and * there is no lookahead cache involved; padding has a toll on short lived or bursty uses * and lookahead doesn't really matter with small queues. * * @param <T> the value type */ final class SpscLinkedArrayQueue<T> extends AbstractQueue<T> implements BiPredicate<T, T> { final int mask; volatile long producerIndex; @SuppressWarnings("rawtypes") static final AtomicLongFieldUpdater<SpscLinkedArrayQueue> PRODUCER_INDEX = AtomicLongFieldUpdater.newUpdater(SpscLinkedArrayQueue.class, "producerIndex"); AtomicReferenceArray<Object> producerArray; volatile long consumerIndex; @SuppressWarnings("rawtypes") static final AtomicLongFieldUpdater<SpscLinkedArrayQueue> CONSUMER_INDEX = AtomicLongFieldUpdater.newUpdater(SpscLinkedArrayQueue.class, "consumerIndex"); AtomicReferenceArray<Object> consumerArray; static final Object NEXT = new Object(); SpscLinkedArrayQueue(int linkSize) { int c = QueueSupplier.ceilingNextPowerOfTwo(Math.max(8, linkSize)); this.producerArray = this.consumerArray = new AtomicReferenceArray<>(c + 1); this.mask = c - 1; } @Override public boolean offer(T e) { Objects.requireNonNull(e); long pi = producerIndex; AtomicReferenceArray<Object> a = producerArray; int m = mask; int offset = (int) (pi + 1) & m; if (a.get(offset) != null) { offset = (int) pi & m; AtomicReferenceArray<Object> b = new AtomicReferenceArray<>(m + 2); producerArray = b; b.lazySet(offset, e); a.lazySet(m + 1, b); a.lazySet(offset, NEXT); PRODUCER_INDEX.lazySet(this, pi + 1); } else { offset = (int) pi & m; a.lazySet(offset, e); PRODUCER_INDEX.lazySet(this, pi + 1); } return true; } /** * Offer two elements at the same time. * <p>Don't use the regular offer() with this at all! * * @param first the first value, not null * @param second the second value, not null * * @return true if the queue accepted the two new values */ @Override public boolean test(T first, T second) { final AtomicReferenceArray<Object> buffer = producerArray; final long p = producerIndex; final int m = mask; int pi = (int) (p + 2) & m; if (null != buffer.get(pi)) { final AtomicReferenceArray<Object> newBuffer = new AtomicReferenceArray<>(m + 2); producerArray = newBuffer; pi = (int) p & m; newBuffer.lazySet(pi + 1, second);// StoreStore newBuffer.lazySet(pi, first); buffer.lazySet(buffer.length() - 1, newBuffer); buffer.lazySet(pi, NEXT); // new buffer is visible after element is PRODUCER_INDEX.lazySet(this, p + 2);// this ensures correctness on 32bit // platforms } else { pi = (int) p & m; buffer.lazySet(pi + 1, second); buffer.lazySet(pi, first); PRODUCER_INDEX.lazySet(this, p + 2); } return true; } @SuppressWarnings("unchecked") @Override public T poll() { long ci = consumerIndex; AtomicReferenceArray<Object> a = consumerArray; int m = mask; int offset = (int) ci & m; Object o = a.get(offset); if (o == null) { return null; } if (o == NEXT) { AtomicReferenceArray<Object> b = (AtomicReferenceArray<Object>) a.get(m + 1); a.lazySet(m + 1, null); o = b.get(offset); a = b; consumerArray = b; } a.lazySet(offset, null); CONSUMER_INDEX.lazySet(this, ci + 1); return (T) o; } @SuppressWarnings("unchecked") @Override public T peek() { long ci = consumerIndex; AtomicReferenceArray<Object> a = consumerArray; int m = mask; int offset = (int) ci & m; Object o = a.get(offset); if (o == null) { return null; } if (o == NEXT) { a = (AtomicReferenceArray<Object>) a.get(m + 1); o = a.get(offset); } return (T) o; } @Override public boolean isEmpty() { return producerIndex == consumerIndex; } @Override public int size() { long ci = consumerIndex; for (; ; ) { long pi = producerIndex; long ci2 = consumerIndex; if (ci == ci2) { return (int) (pi - ci); } ci = ci2; } } @Override public void clear() { while (poll() != null && !isEmpty()) { } } @Override public Iterator<T> iterator() { throw new UnsupportedOperationException(); } }