1. 概要

このチュートリアルでは、スレッドセーフな HashSet インスタンスを作成する可能性と、HashSetConcurrentHashMapに相当するものを確認します。 さらに、各アプローチの長所と短所を見ていきます。

2. ConcurrentHashMapファクトリメソッドを使用したスレッドセーフHashSet

まず、静的な newKeySet()メソッドを公開したConcurrentHashMapクラスを見ていきます。 基本的に、このメソッドは java.util.Set インターフェースを尊重するインスタンスを返し、 add()、contains()、などの標準メソッドの使用を許可します。

これは、次のように簡単に作成できます。

Set<Integer> threadSafeUniqueNumbers = ConcurrentHashMap.newKeySet();
threadSafeUniqueNumbers.add(23);
threadSafeUniqueNumbers.add(45);

さらに、返されるSetのパフォーマンスは、両方ともハッシュベースのアルゴリズムを使用して実装されるため、HashSetと同様です。さらに、実装が[ X255X]ConcurrentHashMap。

最後に、欠点は、メソッドが Java8からのみ存在することです。

3. ConcurrentHashMapインスタンスメソッドを使用したスレッドセーフHashSet

これまで、静的な方法を見てきました ConcurrentHashMap。 次に、で利用可能なインスタンスメソッドに取り組みます ConcurrentHashMap スレッドセーフを作成するには設定インスタンス。 newKeySet() newKeySet(defaultValue)の2つのメソッドを使用できますが、これらは互いにわずかに異なります。

どちらの方法でも、元のマップにリンクされているセットが作成されます。 言い換えると、元の ConcurrentHashMapに新しいエントリを追加するたびに、 Setはその値を受け取ります。 さらに、これら2つの方法の違いを見てみましょう。

3.1. newKeySet()メソッド

上記のように、 newKeySet()は、元のマップのすべてのキーを含むSetを公開します。 このメソッドとnewKeySet(defaultValue)の主な違いは、現在のメソッドはSetへの新しい要素の追加をサポートしていないことです。 add()やaddAll()と同様に、UnsupportedOperationException。を取得します。

remove(object) clear()などの操作は期待どおりに機能していますが、Setでの変更はすべてに反映されることに注意する必要があります。元の地図:

ConcurrentHashMap<Integer,String> numbersMap = new ConcurrentHashMap<>();
Set<Integer> numbersSet = numbersMap.keySet();

numbersMap.put(1, "One");
numbersMap.put(2, "Two");
numbersMap.put(3, "Three");

System.out.println("Map before remove: "+ numbersMap);
System.out.println("Set before remove: "+ numbersSet);

numbersSet.remove(2);

System.out.println("Set after remove: "+ numbersSet);
System.out.println("Map after remove: "+ numbersMap);

次は上記のコードの出力です:

Map before remove: {1=One, 2=Two, 3=Three}
Set before remove: [1, 2, 3]

Set after remove: [1, 3]
Map after remove: {1=One, 3=Three}

3.2. newKeySet(defaultValue)メソッド

マップ内のキーからSetを作成する別の方法を見てみましょう。 上記のものと比較して、newKeySet(defaultValue)は、セットでadd()またはaddAll()を呼び出すことによって新しい要素の追加をサポートするSetインスタンスを返します。

パラメータとして渡されたデフォルト値をさらに見ると、これは add()または addAll()メソッドで追加されたマップの新しいエントリごとの値として使用されます。 次の例は、これがどのように機能するかを示しています。

ConcurrentHashMap<Integer,String> numbersMap = new ConcurrentHashMap<>();
Set<Integer> numbersSet = numbersMap.keySet("SET-ENTRY");

numbersMap.put(1, "One");
numbersMap.put(2, "Two");
numbersMap.put(3, "Three");

System.out.println("Map before add: "+ numbersMap);
System.out.println("Set before add: "+ numbersSet);

numbersSet.addAll(asList(4,5));

System.out.println("Map after add: "+ numbersMap);
System.out.println("Set after add: "+ numbersSet);

以下は、上記のコードの出力です。

Map before add: {1=One, 2=Two, 3=Three}
Set before add: [1, 2, 3]
Map after add: {1=One, 2=Two, 3=Three, 4=SET-ENTRY, 5=SET-ENTRY}
Set after add: [1, 2, 3, 4, 5]

4. コレクションユーティリティクラスを使用したスレッドセーフHashSet

java .util.Collectionsで使用可能なsynchronizedSet()メソッドを使用して、スレッドセーフなHashSetインスタンスを作成しましょう。

Set<Integer> syncNumbers = Collections.synchronizedSet(new HashSet<>());
syncNumbers.add(1);

このアプローチを使用する前に、上記のよりも効率が低いことに注意する必要があります。 基本的に、 synchronizedSet()は、低レベルの同時実行メカニズムを実装する ConcurrentHashMap と比較して、Setインスタンスを同期デコレーターにラップするだけです。

5. CopyOnWriteArraySetを使用したスレッドセーフSet

スレッドセーフなSet実装を作成するための最後のアプローチは、CopyOnWriteArraySetです。 このSetのインスタンスの作成は簡単です。

Set<Integer> copyOnArraySet = new CopyOnWriteArraySet<>();
copyOnArraySet.add(1);

このクラスを使用することは魅力的に見えますが、いくつかの重大なパフォーマンス上の欠点を考慮する必要があります。 舞台裏では、 CopyOnWriteArraySet は、 HashMap、ではなく Array、を使用してデータを格納します。 これは、contains()やremove()などの操作の複雑さがO(n)であることを意味しますが、ConcurrentHashMapに基づくセットを使用する場合、複雑さはO(1)です。

Set のサイズが一般的に小さく、読み取り専用操作が過半数を占める場合は、この実装を使用することをお勧めします。

6. 結論

この記事では、スレッドセーフな Set インスタンスを作成するさまざまな可能性を見て、それらの違いを強調しました。 まず、ConcurrentHashMap.newKeySet()静的メソッドを見てきました。 これは、スレッドセーフなHashSetが必要な場合の最初の選択肢です。 。 その後、 ConcurrentHashMap静的メソッドとConcurrentHashMapインスタンスのnewKeySet()、newKeySet(defaultValue)の違いを調べました。

最後に、Collections。 synchronizedSet() CopyOnWriteArraySet についても説明しましたが、パフォーマンス上の欠点があります。

いつものように、完全なソースコードはGitHubから入手できます。