com.firefly.utils.collection.ConcurrentArrayQueue Maven / Gradle / Ivy
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.
*
* 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.
*
*
* The algorithm used is a variation of the algorithm from Gidenstam, Sundell
* and Tsigas
* (http://www.adm.hb.se/~AGD/Presentations/CacheAwareQueue_OPODIS.pdf).
*
*
* @param
* the Array entry type
*/
public class ConcurrentArrayQueue extends AbstractQueue {
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> _blocks = new AtomicReferenceArray<>(TAIL_OFFSET + 1);
private final int _blockSize;
public ConcurrentArrayQueue() {
this(DEFAULT_BLOCK_SIZE);
}
public ConcurrentArrayQueue(int blockSize) {
_blockSize = blockSize;
Block block = newBlock();
_blocks.set(HEAD_OFFSET, block);
_blocks.set(TAIL_OFFSET, block);
}
public int getBlockSize() {
return _blockSize;
}
protected Block getHeadBlock() {
return _blocks.get(HEAD_OFFSET);
}
protected Block getTailBlock() {
return _blocks.get(TAIL_OFFSET);
}
@Override
public boolean offer(T item) {
item = Objects.requireNonNull(item);
final Block initialTailBlock = getTailBlock();
Block currentTailBlock = initialTailBlock;
int tail = currentTailBlock.tail();
while (true) {
if (tail == getBlockSize()) {
Block 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 oldTailBlock, Block 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 current, Block update) {
return _blocks.compareAndSet(TAIL_OFFSET, current, update);
}
@SuppressWarnings("unchecked")
@Override
public T poll() {
final Block initialHeadBlock = getHeadBlock();
Block currentHeadBlock = initialHeadBlock;
int head = currentHeadBlock.head();
T result = null;
while (true) {
if (head == getBlockSize()) {
Block 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 oldHeadBlock, Block 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 current, Block update) {
return _blocks.compareAndSet(HEAD_OFFSET, current, update);
}
@Override
public T peek() {
Block currentHeadBlock = getHeadBlock();
int head = currentHeadBlock.head();
while (true) {
if (head == getBlockSize()) {
Block 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 currentHeadBlock = getHeadBlock();
int head = currentHeadBlock.head();
boolean result = false;
while (true) {
if (head == getBlockSize()) {
Block 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 iterator() {
final List