也许这是一个愚蠢的问题,但我似乎找不到明显的答案。
我需要一个只包含唯一值的并发FIFO队列。尝试添加队列中已存在的值只会忽略该值。哪个,如果不是线程安全将是微不足道的。 Java中是否存在数据结构,或者可能是表示此行为的互联网上的代码片段?
答案 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());
}
}