これはばかげた質問かもしれませんが、明白な答えを見つけることができないようです。
一意の値のみを含む同時FIFOキューが必要です。キューにすでに存在する値を追加しようとすると、その値が無視されます。スレッドセーフがなければ、これは簡単です。 Javaにデータ構造がありますか、それともこの動作を示すインターウェブ上のコードスニピットですか?
完全同期よりも優れた同時実行性が必要な場合は、ConcurrentHashMapをバッキングマップとして使用して、それを実行する方法が1つあります。以下はスケッチのみです。
_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 を使用しています。
これを行う組み込みのコレクションはありません。同時Set
と一緒に使用できる同時Queue
実装がいくつかあります。
たとえば、アイテムがセットに正常に追加された後にのみキューに追加され、キューから削除された各アイテムがセットから削除されます。この場合、論理的には、キューの内容は実際にはセット内にあるものであり、キューは順序を追跡し、見つかった効率的なtake()
およびpoll()
操作を提供するために使用されます。 BlockingQueue
でのみ。
Java.util.concurrent.ConcurrentLinkedQueue は、ほとんどの方法でそこに到達します。
ConcurrentLinkedQueueを、追加の一意性をチェックする独自のクラスでラップします。コードはスレッドセーフである必要があります。
代替案を検討するのに十分な正当性が得られるまで、同期されたLinkedHashSetを使用します。より並行したソリューションが提供できる主な利点は、ロックの分割です。
最も単純な同時アプローチは、ConcurrentHashMap(セットとして機能)とConcurrentLinkedQueueです。操作の順序は、必要な制約を提供します。オファー()は最初にCHM#putIfAbsent()を実行し、成功した場合はCLQに挿入します。 poll()はCLQから取得し、CHMから削除します。これは、キュー内のエントリがマップ内にあり、CLQが順序を提供している場合、そのエントリを検討することを意味します。次に、マップのconcurrencyLevelを増やすことで、パフォーマンスを調整できます。追加の際どいことに寛容な場合は、安価なCHM#get()が妥当な前提条件として機能する可能性があります(ただし、ビューが少し古くなると問題が発生する可能性があります)。
Setセマンティクスを使用した並行キューとはどういう意味ですか? (スレッドセーフな構造ではなく)真に並行した構造を意味する場合、私はあなたがポニーを求めていると主張します。
たとえば、put(element)
を呼び出して、すぐに削除される何かがすでにそこにあることを検出した場合はどうなりますか?たとえば、offer(element) || queue.contains(element)
がfalse
を返す場合、それはどういう意味ですか?
これらの種類のことは、コンカレントワールドでは、世界を停止(ロックダウン)しない限り、見た目とはまったく異なることが多いため、少し異なる考え方をする必要があります。そうでなければ、あなたは通常過去に何かを見ています。それで、あなたは実際に何をしようとしていますか?
おそらく拡張 ArrayBlockingQueue 。 (package-access)ロックにアクセスするには、サブクラスを同じパッケージ内に配置する必要がありました。警告:私はこれをテストしていません。
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();
}
}
}
一意のオブジェクトのキューに対する簡単な答えは次のとおりです。
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());
}
}