繁体   English   中英

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

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

我正在寻找一个集合:

  • 是一个Deque / List - 即支持在“顶部”插入元素(最新的项目到顶部) - deque.addFirst(..) / list.add(0, ..) 它可以是一个Queue ,但迭代顺序应该是相反的 - 即最近添加的项目应该放在最前面。
  • 有界 - 即限制为 20 个项目
  • 达到容量时自动丢弃最旧的项目(那些“在底部”,首先添加)
  • 非阻塞 - 如果双端队列为空,则检索不应阻塞。 它也不应该阻塞/返回假/空/抛出异常是双端队列已满。
  • 并发 - 多个线程应该能够对其进行操作

我可以将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.

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