[英]Bounded, auto-discarding, non-blocking, concurrent collection
I'm looking for a collection that:我正在寻找一个集合:
Deque
/ List
- ie supports inserting elements at "the top" (newest items go to the top) - deque.addFirst(..)
/ list.add(0, ..)
.Deque
/ List
- 即支持在“顶部”插入元素(最新的项目到顶部) - deque.addFirst(..)
/ list.add(0, ..)
。 It could be a Queue
, but the iteration order should be reverse - ie the most recently added items should come first.Queue
,但迭代顺序应该是相反的 - 即最近添加的项目应该放在最前面。I can take LinkedBlockingDeque
and wrap it into my custom collection that, on add
operations checks the size and discards the last item(s).我可以将
LinkedBlockingDeque
包装到我的自定义集合中,在add
操作时检查大小并丢弃最后一个项目。 Is there a better option?有更好的选择吗?
I made this simple imeplementation:我做了这个简单的实现:
public class AutoDiscardingDeque<E> extends LinkedBlockingDeque<E> {
public AutoDiscardingDeque() {
super();
}
public AutoDiscardingDeque(int capacity) {
super(capacity);
}
@Override
public synchronized boolean offerFirst(E e) {
if (remainingCapacity() == 0) {
removeLast();
}
super.offerFirst(e);
return true;
}
}
For my needs this suffices, but it should be well-documented methods different than addFirst
/ offerFirst
are still following the semantics of a blocking deque.对于我的需要,这已经足够了,但它应该是有据可查的方法,不同于
addFirst
/ offerFirst
仍然遵循阻塞双端队列的语义。
I believe what you're looking for is a bounded stack.我相信你正在寻找的是一个有界堆栈。 There isn't a core library class that does this, so I think the best way of doing this is to take a non-synchronized stack (LinkedList) and wrap it in a synchronized collection that does the auto-discard and returning null on empty pop.
没有执行此操作的核心库类,因此我认为这样做的最佳方法是采用非同步堆栈(LinkedList)并将其包装在同步集合中,该集合执行自动丢弃并在空时返回 null流行音乐。 Something like this:
像这样的东西:
import java.util.Iterator;
import java.util.LinkedList;
public class BoundedStack<T> implements Iterable<T> {
private final LinkedList<T> ll = new LinkedList<T>();
private final int bound;
public BoundedStack(int bound) {
this.bound = bound;
}
public synchronized void push(T item) {
ll.push(item);
if (ll.size() > bound) {
ll.removeLast();
}
}
public synchronized T pop() {
return ll.poll();
}
public synchronized Iterator<T> iterator() {
return ll.iterator();
}
}
...adding methods like isEmpty as required, if you want it to implement eg List. ...根据需要添加诸如 isEmpty 之类的方法,如果您希望它实现例如 List。
The simplest and classic solution is a bounded ring buffer that overrides the oldest elements.最简单和经典的解决方案是覆盖最旧元素的有界环形缓冲区。
The implementation is rather easy.实现是比较容易的。 You need one AtomicInteger/Long for index + AtomicReferenceArray and you have a lock free general purpose stack with 2 methods only
offer/poll
, no size()
.您需要一个 AtomicInteger/Long for index + AtomicReferenceArray 并且您有一个无锁的通用堆栈,其中包含 2 个方法,只有
offer/poll
,没有size()
。 Most concurrent/lock-free structures have hardships w/ size().大多数并发/无锁结构都有 size() 的困难。 Non-overriding stack can have O(1) but w/ an allocation on put.
非覆盖堆栈可以有 O(1) 但在 put 上分配。
Something along the lines of:类似的东西:
package bestsss.util;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReferenceArray;
public class ConcurrentArrayStack<E> extends AtomicReferenceArray<E>{
//easy to extend and avoid indirections,
//feel free to contain the ConcurrentArrayStack if feel purist
final AtomicLong index = new AtomicLong(-1);
public ConcurrentArrayStack(int length) {
super(length); //returns
}
/**
* @param e the element to offer into the stack
* @return the previously evicted element
*/
public E offer(E e){
for (;;){
long i = index.get();
//get the result, CAS expect before the claim
int idx = idx(i+1);
E result = get(idx);
if (!index.compareAndSet(i, i+1))//claim index spot
continue;
if (compareAndSet(idx, result, e)){
return result;
}
}
}
private int idx(long idx){//can/should use golden ratio to spread the index around and reduce false sharing
return (int)(idx%length());
}
public E poll(){
for (;;){
long i = index.get();
if (i==-1)
return null;
int idx = idx(i);
E result = get(idx);//get before the claim
if (!index.compareAndSet(i, i-1))//claim index spot
continue;
if (compareAndSet(idx, result, null)){
return result;
}
}
}
}
Last note: having mod operation is an expensive one and power-of-2 capacity is to preferred, via &length()-1
(also guards vs long overflow).最后一点:通过
&length()-1
(也是守卫 vs 长溢出),进行 mod 操作是一项昂贵的操作,并且首选 2 次方的容量。
Here is an implementation that handles concurrency and never returns Null.这是一个处理并发并且从不返回 Null 的实现。
import com.google.common.base.Optional;
import java.util.Deque;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.locks.ReentrantLock;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
public class BoundedStack<T> {
private final Deque<T> list = new ConcurrentLinkedDeque<>();
private final int maxEntries;
private final ReentrantLock lock = new ReentrantLock();
public BoundedStack(final int maxEntries) {
checkArgument(maxEntries > 0, "maxEntries must be greater than zero");
this.maxEntries = maxEntries;
}
public void push(final T item) {
checkNotNull(item, "item must not be null");
lock.lock();
try {
list.push(item);
if (list.size() > maxEntries) {
list.removeLast();
}
} finally {
lock.unlock();
}
}
public Optional<T> pop() {
lock.lock();
try {
return Optional.ofNullable(list.poll());
} finally {
lock.unlock();
}
}
public Optional<T> peek() {
return Optional.fromNullable(list.peekFirst());
}
public boolean empty() {
return list.isEmpty();
}
}
For the solution @remery gave, could you not run into a race condition where after if (list.size() > maxEntries)
you could erroneously remove the last element if another thread runs pop()
in that time period and the list is now within capacity.对于@remery 给出的解决方案,您是否会遇到竞争条件,在
if (list.size() > maxEntries)
,如果另一个线程在该时间段内运行pop()
并且列表是现在,您可能会错误地删除最后一个元素在能力范围内。 Given there is no thread synchronization across pop()
and public void push(final T item)
.鉴于
pop()
和public void push(final T item)
之间没有线程同步。
For the solution @Bozho gave I would think a similar scenario could be possible?对于@Bozho 给出的解决方案,我认为可能出现类似的情况? The synchronization is happening on the
AutoDiscardingDeque
and not with the ReentrantLock
inside LinkedBlockingDeque
so after running remainingCapacity()
another thread could remove some objects from the list and the removeLast()
would remove an extra object?同步发生在
AutoDiscardingDeque
上,而不是与ReentrantLock
内的ReentrantLock
发生同步,所以在运行remainingCapacity()
LinkedBlockingDeque
remainingCapacity()
另一个线程可以从列表中删除一些对象,而removeLast()
会删除一个额外的对象?
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.