All Downloads are FREE. Search and download functionalities are using the official Maven repository.

rx.internal.util.atomic.SpscUnboundedAtomicArrayQueue Maven / Gradle / Ivy

There is a newer version: 1.3.8
Show newest version
/*
 * 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.
 * 
 * Original License: https://github.com/JCTools/JCTools/blob/master/LICENSE
 * Original location: https://github.com/JCTools/JCTools/blob/master/jctools-core/src/main/java/org/jctools/queues/atomic/SpscUnboundedAtomicArrayQueue.java
 */

package rx.internal.util.atomic;

import java.util.*;
import java.util.concurrent.atomic.*;

import rx.internal.util.unsafe.Pow2;

/**
 * A single-producer single-consumer queue with unbounded capacity.
 * 

The implementation uses fixed, power-of-2 arrays to store elements and turns into a linked-list like * structure if the production overshoots the consumption. *

Note that the minimum capacity of the 'islands' are 8 due to how the look-ahead optimization works. *

The implementation uses field updaters and thus should be platform-safe. * @param the value type held by this queue */ public final class SpscUnboundedAtomicArrayQueue implements Queue { static final int MAX_LOOK_AHEAD_STEP = Integer.getInteger("jctools.spsc.max.lookahead.step", 4096); final AtomicLong producerIndex; int producerLookAheadStep; long producerLookAhead; int producerMask; AtomicReferenceArray producerBuffer; int consumerMask; AtomicReferenceArray consumerBuffer; final AtomicLong consumerIndex; private static final Object HAS_NEXT = new Object(); public SpscUnboundedAtomicArrayQueue(final int bufferSize) { int p2capacity = Pow2.roundToPowerOfTwo(Math.max(8, bufferSize)); // lookahead doesn't work with capacity < 8 int mask = p2capacity - 1; this.producerIndex = new AtomicLong(); this.consumerIndex = new AtomicLong(); AtomicReferenceArray buffer = new AtomicReferenceArray(p2capacity + 1); producerBuffer = buffer; producerMask = mask; adjustLookAheadStep(p2capacity); consumerBuffer = buffer; consumerMask = mask; producerLookAhead = mask - 1; // we know it's all empty to start with soProducerIndex(0L); } /** * {@inheritDoc} *

* This implementation is correct for single producer thread use only. */ @Override public boolean offer(final T e) { if (e == null) { throw new NullPointerException(); } // local load of field to avoid repeated loads after volatile reads final AtomicReferenceArray buffer = producerBuffer; final long index = lpProducerIndex(); final int mask = producerMask; final int offset = calcWrappedOffset(index, mask); if (index < producerLookAhead) { return writeToQueue(buffer, e, index, offset); } else { final int lookAheadStep = producerLookAheadStep; // go around the buffer or resize if full (unless we hit max capacity) int lookAheadElementOffset = calcWrappedOffset(index + lookAheadStep, mask); if (null == lvElement(buffer, lookAheadElementOffset)) {// LoadLoad producerLookAhead = index + lookAheadStep - 1; // joy, there's plenty of room return writeToQueue(buffer, e, index, offset); } else if (null != lvElement(buffer, calcWrappedOffset(index + 1, mask))) { // buffer is not full return writeToQueue(buffer, e, index, offset); } else { resize(buffer, index, offset, e, mask); // add a buffer and link old to new return true; } } } private boolean writeToQueue(final AtomicReferenceArray buffer, final T e, final long index, final int offset) { soProducerIndex(index + 1);// this ensures atomic write of long on 32bit platforms soElement(buffer, offset, e);// StoreStore return true; } private void resize(final AtomicReferenceArray oldBuffer, final long currIndex, final int offset, final T e, final long mask) { final int capacity = oldBuffer.length(); final AtomicReferenceArray newBuffer = new AtomicReferenceArray(capacity); producerBuffer = newBuffer; producerLookAhead = currIndex + mask - 1; soProducerIndex(currIndex + 1);// this ensures correctness on 32bit platforms soElement(newBuffer, offset, e);// StoreStore soNext(oldBuffer, newBuffer); soElement(oldBuffer, offset, HAS_NEXT); // new buffer is visible after element is // inserted } private void soNext(AtomicReferenceArray curr, AtomicReferenceArray next) { soElement(curr, calcDirectOffset(curr.length() - 1), next); } @SuppressWarnings("unchecked") private AtomicReferenceArray lvNext(AtomicReferenceArray curr) { return (AtomicReferenceArray)lvElement(curr, calcDirectOffset(curr.length() - 1)); } /** * {@inheritDoc} *

* This implementation is correct for single consumer thread use only. */ @SuppressWarnings("unchecked") @Override public T poll() { // local load of field to avoid repeated loads after volatile reads final AtomicReferenceArray buffer = consumerBuffer; final long index = lpConsumerIndex(); final int mask = consumerMask; final int offset = calcWrappedOffset(index, mask); final Object e = lvElement(buffer, offset);// LoadLoad boolean isNextBuffer = e == HAS_NEXT; if (null != e && !isNextBuffer) { soConsumerIndex(index + 1);// this ensures correctness on 32bit platforms soElement(buffer, offset, null);// StoreStore return (T) e; } else if (isNextBuffer) { return newBufferPoll(lvNext(buffer), index, mask); } return null; } @SuppressWarnings("unchecked") private T newBufferPoll(AtomicReferenceArray nextBuffer, final long index, final int mask) { consumerBuffer = nextBuffer; final int offsetInNew = calcWrappedOffset(index, mask); final T n = (T) lvElement(nextBuffer, offsetInNew);// LoadLoad if (null == n) { return null; } else { soConsumerIndex(index + 1);// this ensures correctness on 32bit platforms soElement(nextBuffer, offsetInNew, null);// StoreStore return n; } } /** * {@inheritDoc} *

* This implementation is correct for single consumer thread use only. */ @SuppressWarnings("unchecked") @Override public T peek() { final AtomicReferenceArray buffer = consumerBuffer; final long index = lpConsumerIndex(); final int mask = consumerMask; final int offset = calcWrappedOffset(index, mask); final Object e = lvElement(buffer, offset);// LoadLoad if (e == HAS_NEXT) { return newBufferPeek(lvNext(buffer), index, mask); } return (T) e; } @Override public void clear() { while (poll() != null || !isEmpty()); // NOPMD } @SuppressWarnings("unchecked") private T newBufferPeek(AtomicReferenceArray nextBuffer, final long index, final int mask) { consumerBuffer = nextBuffer; final int offsetInNew = calcWrappedOffset(index, mask); return (T) lvElement(nextBuffer, offsetInNew);// LoadLoad } @Override public int size() { /* * It is possible for a thread to be interrupted or reschedule between the read of the producer and * consumer indices, therefore protection is required to ensure size is within valid range. In the * event of concurrent polls/offers to this method the size is OVER estimated as we read consumer * index BEFORE the producer index. */ long after = lvConsumerIndex(); while (true) { final long before = after; final long currentProducerIndex = lvProducerIndex(); after = lvConsumerIndex(); if (before == after) { return (int) (currentProducerIndex - after); } } } @Override public boolean isEmpty() { return lvProducerIndex() == lvConsumerIndex(); } private void adjustLookAheadStep(int capacity) { producerLookAheadStep = Math.min(capacity / 4, MAX_LOOK_AHEAD_STEP); } private long lvProducerIndex() { return producerIndex.get(); } private long lvConsumerIndex() { return consumerIndex.get(); } private long lpProducerIndex() { return producerIndex.get(); } private long lpConsumerIndex() { return consumerIndex.get(); } private void soProducerIndex(long v) { producerIndex.lazySet(v); } private void soConsumerIndex(long v) { consumerIndex.lazySet(v); } private static int calcWrappedOffset(long index, int mask) { return calcDirectOffset((int)index & mask); } private static int calcDirectOffset(int index) { return index; } private static void soElement(AtomicReferenceArray buffer, int offset, Object e) { buffer.lazySet(offset, e); } private static Object lvElement(AtomicReferenceArray buffer, int offset) { return buffer.get(offset); } @Override public Iterator iterator() { throw new UnsupportedOperationException(); } @Override public boolean contains(Object o) { throw new UnsupportedOperationException(); } @Override public Object[] toArray() { throw new UnsupportedOperationException(); } @Override public E[] toArray(E[] a) { throw new UnsupportedOperationException(); } @Override public boolean remove(Object o) { throw new UnsupportedOperationException(); } @Override public boolean containsAll(Collection c) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection c) { throw new UnsupportedOperationException(); } @Override public boolean removeAll(Collection c) { throw new UnsupportedOperationException(); } @Override public boolean retainAll(Collection c) { throw new UnsupportedOperationException(); } @Override public boolean add(T e) { throw new UnsupportedOperationException(); } @Override public T remove() { throw new UnsupportedOperationException(); } @Override public T element() { throw new UnsupportedOperationException(); } }