[英]Bounded, auto-discarding, non-blocking, concurrent collection
我正在寻找一个集合:
Deque
/ List
- 即支持在“顶部”插入元素(最新的项目到顶部) - deque.addFirst(..)
/ list.add(0, ..)
。 它可以是一个Queue
,但迭代顺序应该是相反的 - 即最近添加的项目应该放在最前面。我可以将LinkedBlockingDeque
包装到我的自定义集合中,在add
操作时检查大小并丢弃最后一个项目。 有更好的选择吗?
我做了这个简单的实现:
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;
}
}
对于我的需要,这已经足够了,但它应该是有据可查的方法,不同于addFirst
/ offerFirst
仍然遵循阻塞双端队列的语义。
我相信你正在寻找的是一个有界堆栈。 没有执行此操作的核心库类,因此我认为这样做的最佳方法是采用非同步堆栈(LinkedList)并将其包装在同步集合中,该集合执行自动丢弃并在空时返回 null流行音乐。 像这样的东西:
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();
}
}
...根据需要添加诸如 isEmpty 之类的方法,如果您希望它实现例如 List。
最简单和经典的解决方案是覆盖最旧元素的有界环形缓冲区。
实现是比较容易的。 您需要一个 AtomicInteger/Long for index + AtomicReferenceArray 并且您有一个无锁的通用堆栈,其中包含 2 个方法,只有offer/poll
,没有size()
。 大多数并发/无锁结构都有 size() 的困难。 非覆盖堆栈可以有 O(1) 但在 put 上分配。
类似的东西:
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;
}
}
}
}
最后一点:通过&length()-1
(也是守卫 vs 长溢出),进行 mod 操作是一项昂贵的操作,并且首选 2 次方的容量。
这是一个处理并发并且从不返回 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();
}
}
对于@remery 给出的解决方案,您是否会遇到竞争条件,在if (list.size() > maxEntries)
,如果另一个线程在该时间段内运行pop()
并且列表是现在,您可能会错误地删除最后一个元素在能力范围内。 鉴于pop()
和public void push(final T item)
之间没有线程同步。
对于@Bozho 给出的解决方案,我认为可能出现类似的情况? 同步发生在AutoDiscardingDeque
上,而不是与ReentrantLock
内的ReentrantLock
发生同步,所以在运行remainingCapacity()
LinkedBlockingDeque
remainingCapacity()
另一个线程可以从列表中删除一些对象,而removeLast()
会删除一个额外的对象?
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.