org.eclipse.jetty.util.ConcurrentArrayQueue Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jetty-util Show documentation
Show all versions of jetty-util Show documentation
Utility classes for Jetty
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.util;
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;
/**
* 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
*/
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);
}
@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
{
Object element = currentHeadBlock.peek(head);
if (element == REMOVED_ELEMENT)
{
// Already removed, try next index
++head;
}
else
{
return (T)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