有界,自动丢弃,无阻塞,并发收集

时间:2010-10-23 08:59:48

标签: java collections

我正在寻找一个集合:

  • Deque / List - 即支持在“顶部”插入元素(最新项目位于顶部) - deque.addFirst(..) / list.add(0, ..)。它可能是Queue,但迭代顺序应该是反向的 - 即最近添加的项应该是第一个。
  • 是有界的 - 即限制为20项
  • 在达到容量时自动丢弃最旧的项目(在底部“,先添加”)
  • 非阻塞 - 如果双端队列为空,则不应阻止检索。它也不应该阻塞/返回false / null / throw异常是deque已满。
  • 并发 - 多个线程应该能够在其上运行

我可以将LinkedBlockingDeque包装到我的自定义集合中,在add操作上检查大小并丢弃最后一项。有更好的选择吗?

5 个答案:

答案 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()中删除一些对象。会删除一个多余的对象?