我正在寻找一个集合:
Deque
/ List
- 即支持在“顶部”插入元素(最新项目位于顶部) - deque.addFirst(..)
/ list.add(0, ..)
。它可能是Queue
,但迭代顺序应该是反向的 - 即最近添加的项应该是第一个。我可以将LinkedBlockingDeque
包装到我的自定义集合中,在add
操作上检查大小并丢弃最后一项。有更好的选择吗?
答案 0 :(得分:8)
我做了这个简单的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;
}
}
根据我的需要,这就足够了,但是应该是与addFirst
/ offerFirst
不同的,记录良好的方法仍然遵循阻塞双端队列的语义。
答案 1 :(得分:4)
我相信你所寻找的是一个有限的堆栈。没有一个核心库类可以做到这一点,所以我认为最好的方法是采用非同步堆栈(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。
答案 2 :(得分:3)
最简单和经典的解决方案是有界环形缓冲区,它覆盖了最古老的元素。
实施相当容易。你需要一个AtomicInteger / Long用于索引+ AtomicReferenceArray,你有一个无锁的通用栈,只有2个方法offer/poll
,没有size()
。大多数并发/无锁结构具有w / size()的困难。非重写堆栈可以具有O(1)但是在放置时具有分配。
有些事情:
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;
}
}
}
}
最后说明:
拥有mod操作是一个昂贵的操作,并且首选容量为2,通过&length()-1
(也是防护与长溢出)。
答案 3 :(得分: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() {
return Optional.fromNullable(list.poll());
}
public Optional<T> peek() {
return Optional.fromNullable(list.peekFirst());
}
public boolean empty() {
return list.isEmpty();
}
}
答案 4 :(得分:1)
对于@remery给出的解决方案,您是否无法进入竞争状态,在if (list.size() > maxEntries)
之后,如果另一个线程在该时间段内运行pop()
,则您可能会错误地删除最后一个元素,并且列表现在在能力范围内。假设pop()
和public void push(final T item)
之间没有线程同步。
对于解决方案,@ Bozho给出了类似的方案吗?同步发生在AutoDiscardingDeque
上,而不是ReentrantLock
内的LinkedBlockingDeque
上,因此运行remainingCapacity()
后,另一个线程可能会从列表和removeLast()
中删除一些对象。会删除一个多余的对象?