简体   繁体   English

有界、自动丢弃、非阻塞、并发收集

[英]Bounded, auto-discarding, non-blocking, concurrent collection

I'm looking for a collection that:我正在寻找一个集合:

  • is a 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 ,但迭代顺序应该是相反的 - 即最近添加的项目应该放在最前面。
  • is bounded - ie has a limit of 20 items有界 - 即限制为 20 个项目
  • auto-discards the oldest items (those "at the bottom", added first) when the capacity is reached达到容量时自动丢弃最旧的项目(那些“在底部”,首先添加)
  • non-blocking - if the deque is empty, retrievals should not block.非阻塞 - 如果双端队列为空,则检索不应阻塞。 It should also not block / return false / null / throw exception is the deque is full.它也不应该阻塞/返回假/空/抛出异常是双端队列已满。
  • concurrent - multiple threads should be able to operate on it并发 - 多个线程应该能够对其进行操作

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM