package com.firefly.utils.collection; import java.util.AbstractQueue; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicIntegerArray; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReferenceArray; import com.firefly.utils.concurrent.MemoryUtils; /** * A concurrent, unbounded implementation of {@link Queue} that uses * singly-linked array blocks to store elements. * <p> * This class is a drop-in replacement for {@link ConcurrentLinkedQueue}, with * similar performance but producing less garbage because arrays are used to * store elements rather than nodes. * </p> * <p> * The algorithm used is a variation of the algorithm from Gidenstam, Sundell * and Tsigas * (http://www.adm.hb.se/~AGD/Presentations/CacheAwareQueue_OPODIS.pdf). * </p> * * @param <T> * the Array entry type */ public class ConcurrentArrayQueue<T> extends AbstractQueue<T> { public static final int DEFAULT_BLOCK_SIZE = 512; public static final Object REMOVED_ELEMENT = new Object() { @Override public String toString() { return "X"; } }; private static final int HEAD_OFFSET = MemoryUtils.getIntegersPerCacheLine() - 1; private static final int TAIL_OFFSET = MemoryUtils.getIntegersPerCacheLine() * 2 - 1; private final AtomicReferenceArray<Block<T>> _blocks = new AtomicReferenceArray<>(TAIL_OFFSET + 1); private final int _blockSize; public ConcurrentArrayQueue() { this(DEFAULT_BLOCK_SIZE); } public ConcurrentArrayQueue(int blockSize) { _blockSize = blockSize; Block<T> block = newBlock(); _blocks.set(HEAD_OFFSET, block); _blocks.set(TAIL_OFFSET, block); } public int getBlockSize() { return _blockSize; } protected Block<T> getHeadBlock() { return _blocks.get(HEAD_OFFSET); } protected Block<T> getTailBlock() { return _blocks.get(TAIL_OFFSET); } @Override public boolean offer(T item) { item = Objects.requireNonNull(item); final Block<T> initialTailBlock = getTailBlock(); Block<T> currentTailBlock = initialTailBlock; int tail = currentTailBlock.tail(); while (true) { if (tail == getBlockSize()) { Block<T> nextTailBlock = currentTailBlock.next(); if (nextTailBlock == null) { nextTailBlock = newBlock(); if (currentTailBlock.link(nextTailBlock)) { // Linking succeeded, loop currentTailBlock = nextTailBlock; } else { // Concurrent linking, use other block and loop currentTailBlock = currentTailBlock.next(); } } else { // Not at last block, loop currentTailBlock = nextTailBlock; } tail = currentTailBlock.tail(); } else { if (currentTailBlock.peek(tail) == null) { if (currentTailBlock.store(tail, item)) { // Item stored break; } else { // Concurrent store, try next index ++tail; } } else { // Not free, try next index ++tail; } } } updateTailBlock(initialTailBlock, currentTailBlock); return true; } private void updateTailBlock(Block<T> oldTailBlock, Block<T> newTailBlock) { // Update the tail block pointer if needs to if (oldTailBlock != newTailBlock) { // The tail block pointer is allowed to lag behind. // If this update fails, it means that other threads // have filled this block and installed a new one. casTailBlock(oldTailBlock, newTailBlock); } } protected boolean casTailBlock(Block<T> current, Block<T> update) { return _blocks.compareAndSet(TAIL_OFFSET, current, update); } @SuppressWarnings("unchecked") @Override public T poll() { final Block<T> initialHeadBlock = getHeadBlock(); Block<T> currentHeadBlock = initialHeadBlock; int head = currentHeadBlock.head(); T result = null; while (true) { if (head == getBlockSize()) { Block<T> nextHeadBlock = currentHeadBlock.next(); if (nextHeadBlock == null) { // We could have read that the next head block was null // but another thread allocated a new block and stored a // new item. This thread could not detect this, but that // is ok, otherwise we would not be able to exit this loop. // Queue is empty break; } else { // Use next block and loop currentHeadBlock = nextHeadBlock; head = currentHeadBlock.head(); } } else { Object element = currentHeadBlock.peek(head); if (element == REMOVED_ELEMENT) { // Already removed, try next index ++head; } else { result = (T) element; if (result != null) { if (currentHeadBlock.remove(head, result, true)) { // Item removed break; } else { // Concurrent remove, try next index ++head; } } else { // Queue is empty break; } } } } updateHeadBlock(initialHeadBlock, currentHeadBlock); return result; } private void updateHeadBlock(Block<T> oldHeadBlock, Block<T> newHeadBlock) { // Update the head block pointer if needs to if (oldHeadBlock != newHeadBlock) { // The head block pointer lagged behind. // If this update fails, it means that other threads // have emptied this block and pointed to a new one. casHeadBlock(oldHeadBlock, newHeadBlock); } } protected boolean casHeadBlock(Block<T> current, Block<T> update) { return _blocks.compareAndSet(HEAD_OFFSET, current, update); } @Override public T peek() { Block<T> currentHeadBlock = getHeadBlock(); int head = currentHeadBlock.head(); while (true) { if (head == getBlockSize()) { Block<T> nextHeadBlock = currentHeadBlock.next(); if (nextHeadBlock == null) { // Queue is empty return null; } else { // Use next block and loop currentHeadBlock = nextHeadBlock; head = currentHeadBlock.head(); } } else { T element = currentHeadBlock.peek(head); if (element == REMOVED_ELEMENT) { // Already removed, try next index ++head; } else { return element; } } } } @Override public boolean remove(Object o) { Block<T> currentHeadBlock = getHeadBlock(); int head = currentHeadBlock.head(); boolean result = false; while (true) { if (head == getBlockSize()) { Block<T> nextHeadBlock = currentHeadBlock.next(); if (nextHeadBlock == null) { // Not found break; } else { // Use next block and loop currentHeadBlock = nextHeadBlock; head = currentHeadBlock.head(); } } else { Object element = currentHeadBlock.peek(head); if (element == REMOVED_ELEMENT) { // Removed, try next index ++head; } else { if (element == null) { // Not found break; } else { if (element.equals(o)) { // Found if (currentHeadBlock.remove(head, o, false)) { result = true; break; } else { ++head; } } else { // Not the one we're looking for ++head; } } } } } return result; } @Override public boolean removeAll(Collection<?> c) { // TODO: super invocations are based on iterator.remove(), which throws return super.removeAll(c); } @Override public boolean retainAll(Collection<?> c) { // TODO: super invocations are based on iterator.remove(), which throws return super.retainAll(c); } @Override public Iterator<T> iterator() { final List<Object[]> blocks = new ArrayList<>(); Block<T> currentHeadBlock = getHeadBlock(); while (currentHeadBlock != null) { Object[] elements = currentHeadBlock.arrayCopy(); blocks.add(elements); currentHeadBlock = currentHeadBlock.next(); } return new Iterator<T>() { private int blockIndex; private int index; @Override public boolean hasNext() { while (true) { if (blockIndex == blocks.size()) return false; Object element = blocks.get(blockIndex)[index]; if (element == null) return false; if (element != REMOVED_ELEMENT) return true; advance(); } } @Override public T next() { while (true) { if (blockIndex == blocks.size()) throw new NoSuchElementException(); Object element = blocks.get(blockIndex)[index]; if (element == null) throw new NoSuchElementException(); advance(); if (element != REMOVED_ELEMENT) { @SuppressWarnings("unchecked") T e = (T) element; return e; } } } private void advance() { if (++index == getBlockSize()) { index = 0; ++blockIndex; } } @Override public void remove() { throw new UnsupportedOperationException(); } }; } @Override public int size() { Block<T> currentHeadBlock = getHeadBlock(); int head = currentHeadBlock.head(); int size = 0; while (true) { if (head == getBlockSize()) { Block<T> nextHeadBlock = currentHeadBlock.next(); if (nextHeadBlock == null) { break; } else { // Use next block and loop currentHeadBlock = nextHeadBlock; head = currentHeadBlock.head(); } } else { Object element = currentHeadBlock.peek(head); if (element == REMOVED_ELEMENT) { // Already removed, try next index ++head; } else if (element != null) { ++size; ++head; } else { break; } } } return size; } protected Block<T> newBlock() { return new Block<>(getBlockSize()); } protected int getBlockCount() { int result = 0; Block<T> headBlock = getHeadBlock(); while (headBlock != null) { ++result; headBlock = headBlock.next(); } return result; } protected static final class Block<E> { private static final int headOffset = MemoryUtils.getIntegersPerCacheLine() - 1; private static final int tailOffset = MemoryUtils.getIntegersPerCacheLine() * 2 - 1; private final AtomicReferenceArray<Object> elements; private final AtomicReference<Block<E>> next = new AtomicReference<>(); private final AtomicIntegerArray indexes = new AtomicIntegerArray(TAIL_OFFSET + 1); protected Block(int blockSize) { elements = new AtomicReferenceArray<>(blockSize); } @SuppressWarnings("unchecked") public E peek(int index) { return (E) elements.get(index); } public boolean store(int index, E item) { boolean result = elements.compareAndSet(index, null, item); if (result) indexes.incrementAndGet(tailOffset); return result; } public boolean remove(int index, Object item, boolean updateHead) { boolean result = elements.compareAndSet(index, item, REMOVED_ELEMENT); if (result && updateHead) indexes.incrementAndGet(headOffset); return result; } public Block<E> next() { return next.get(); } public boolean link(Block<E> nextBlock) { return next.compareAndSet(null, nextBlock); } public int head() { return indexes.get(headOffset); } public int tail() { return indexes.get(tailOffset); } public Object[] arrayCopy() { Object[] result = new Object[elements.length()]; for (int i = 0; i < result.length; ++i) result[i] = elements.get(i); return result; } } }