并发集队列

时间:2010-06-25 18:32:51

标签: java collections concurrency queue set

也许这是一个愚蠢的问题,但我似乎找不到明显的答案。

我需要一个只包含唯一值的并发FIFO队列。尝试添加队列中已存在的值只会忽略该值。哪个,如果不是线程安全将是微不足道的。 Java中是否存在数据结构,或者可能是表示此行为的互联网上的代码片段?

7 个答案:

答案 0 :(得分:6)

如果你想要比完全同步更好的并发性,我有一种方法可以做到这一点,使用ConcurrentHashMap作为支持映射。以下是仅草图。

public final class ConcurrentHashSet<E> extends ForwardingSet<E>
    implements Set<E>, Queue<E> {
  private enum Dummy { VALUE }

  private final ConcurrentMap<E, Dummy> map;

  ConcurrentHashSet(ConcurrentMap<E, Dummy> map) {
    super(map.keySet());
    this.map = Preconditions.checkNotNull(map);
  }

  @Override public boolean add(E element) {
    return map.put(element, Dummy.VALUE) == null;
  }

  @Override public boolean addAll(Collection<? extends E> newElements) {
    // just the standard implementation
    boolean modified = false;
    for (E element : newElements) {
      modified |= add(element);
    }
    return modified;
  }

  @Override public boolean offer(E element) {
    return add(element);
  }

  @Override public E remove() {
    E polled = poll();
    if (polled == null) {
      throw new NoSuchElementException();
    }
    return polled;
  }

  @Override public E poll() {
    for (E element : this) {
      // Not convinced that removing via iterator is viable (check this?)
      if (map.remove(element) != null) {
        return element;
      }
    }
    return null;
  }

  @Override public E element() {
    return iterator().next();
  }

  @Override public E peek() {
    Iterator<E> iterator = iterator();
    return iterator.hasNext() ? iterator.next() : null;
  }
}

用这种方法一切都不是阳光。除了使用支持地图的entrySet().iterator().next()之外,我们没有合适的方法来选择头元素,结果是地图随着时间的推移变得越来越不平衡。由于更大的桶冲突和更大的段争用,这种不平衡是一个问题。

注意:此代码在少数地方使用Guava

答案 1 :(得分:5)

没有内置的集合可以做到这一点。有一些并发Set实现可以与并发Queue一起使用。

例如,只有将项目成功添加到集合后才会将项目添加到队列中,并且从集合中删除从队列中删除的每个项目。在这种情况下,队列的内容在逻辑上实际上是集合中的任何内容,并且队列仅用于跟踪顺序并提供仅在{take()poll()操作上发现的有效BlockingQueue和{{1}}操作{1}}。

答案 2 :(得分:4)

java.util.concurrent.ConcurrentLinkedQueue可以帮助你完成大部分工作。

使用您自己的类包装ConcurrentLinkedQueue,该类检查添加的唯一性。您的代码必须是线程安全的。

答案 3 :(得分:4)

我会使用同步的LinkedHashSet,直到有足够的理由来考虑替代方案。更多并发解决方案可以提供的主要好处是锁定拆分。

最简单的并发方法是ConcurrentHashMap(充当集合)和ConcurrentLinkedQueue。操作的顺序将提供所需的约束。 offer()首先执行CHM#putIfAbsent(),如果成功插入CLQ。 poll()将从CLQ中获取,然后将其从CHM中删除。这意味着如果它在映射中并且CLQ提供了排序,我们会考虑队列中的条目。然后可以通过增加map的concurrencyLevel来调整性能。如果你容忍额外的racy-ness,那么便宜的CHM#get()可以作为一个合理的前提条件(但它可能会因为一个稍微陈旧的观点而受到影响)。

答案 4 :(得分:2)

具有Set语义的并发队列是什么意思?如果你的意思是一个真正的并发结构(而不​​是一个线程安全的结构)那么我会争辩说你要求一匹小马。

例如,如果您致电put(element)并检测到某些东西已经被删除,会发生什么?例如,如果offer(element) || queue.contains(element)返回false

,对您来说意味着什么?

在并发的世界中,这些事情往往需要略微不同,因为除非你停止世界(锁定它),否则往往没有任何东西。否则你通常会看到过去的事情。那么,你究竟想做什么?

答案 5 :(得分:0)

也许会延伸ArrayBlockingQueue。为了访问(包访问)锁,我不得不将我的子类放在同一个包中。警告:我没有测试过这个。

package java.util.concurrent;

import java.util.Collection;
import java.util.concurrent.locks.ReentrantLock;

public class DeDupingBlockingQueue<E> extends ArrayBlockingQueue<E> {

    public DeDupingBlockingQueue(int capacity) {
        super(capacity);
    }

    public DeDupingBlockingQueue(int capacity, boolean fair) {
        super(capacity, fair);
    }

    public DeDupingBlockingQueue(int capacity, boolean fair, Collection<? extends E> c) {
        super(capacity, fair, c);
    }

    @Override
    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            if (contains(e)) return false;
            return super.add(e);
        } finally {
            lock.unlock();
        }
    }

    @Override
    public boolean offer(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            if (contains(e)) return true;
            return super.offer(e);
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void put(E e) throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly(); //Should this be lock.lock() instead?
        try {
            if (contains(e)) return;
            super.put(e); //if it blocks, it does so without holding the lock.
        } finally {
            lock.unlock();
        }
    }

    @Override
    public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            if (contains(e)) return true;
            return super.offer(e, timeout, unit); //if it blocks, it does so without holding the lock.
        } finally {
            lock.unlock();
        }
    }
}

答案 6 :(得分:0)

对于唯一对象队列的简单答案可以如下:

import java.util.concurrent.ConcurrentLinkedQueue;

public class FinalQueue {

    class Bin {
        private int a;
        private int b;

        public Bin(int a, int b) {
            this.a = a;
            this.b = b;
        }

        @Override
        public int hashCode() {
            return a * b;
        }

        public String toString() {
            return a + ":" + b;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            Bin other = (Bin) obj;
            if ((a != other.a) || (b != other.b))
                return false;
            return true;
        }
    }

    private ConcurrentLinkedQueue<Bin> queue;

    public FinalQueue() {
        queue = new ConcurrentLinkedQueue<Bin>();
    }

    public synchronized void enqueue(Bin ipAddress) {
        if (!queue.contains(ipAddress))
            queue.add(ipAddress);
    }

    public Bin dequeue() {
        return queue.poll();
    }

    public String toString() {
        return "" + queue;
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        FinalQueue queue = new FinalQueue();
        Bin a = queue.new Bin(2,6);

        queue.enqueue(a);
        queue.enqueue(queue.new Bin(13, 3));
        queue.enqueue(queue.new Bin(13, 3));
        queue.enqueue(queue.new Bin(14, 3));
        queue.enqueue(queue.new Bin(13, 9));
        queue.enqueue(queue.new Bin(18, 3));
        queue.enqueue(queue.new Bin(14, 7));
        Bin x= queue.dequeue();
        System.out.println(x.a);
        System.out.println(queue.toString());
        System.out.println("Dequeue..." + queue.dequeue());
        System.out.println("Dequeue..." + queue.dequeue());
        System.out.println(queue.toString());
    }
}