Collections.synchronizedMapとConcurrentHashMap
1. 概要
このチュートリアルでは、 Collections.synchronizedMap()と ConcurrentHashMap。の違いについて説明します。
さらに、それぞれの読み取りおよび書き込み操作のパフォーマンス出力を確認します。
2. 違い
Collections.synchronizedMap()と ConcurrentHashMap はどちらも、データのコレクションに対してスレッドセーフな操作を提供します。
Collections ユーティリティクラスは、コレクションを操作してラップされたコレクションを返すポリモーフィックアルゴリズムを提供します。 そのsynchronizedMap()メソッドは、スレッドセーフな機能を提供します。
名前が示すように、 synchronizedMap()は、パラメーターで指定したMapに基づく同期されたMapを返します。 スレッドセーフを提供するために、 synchronizedMap()は、返されたMapを介したバッキングMapへのすべてのアクセスを許可します。
ConcurrentHashMap は、検索と更新の高い同時実行性をサポートするHashMapの拡張機能としてJDK1.5で導入されました。 HashMap はスレッドセーフではないため、スレッドの競合中に誤った結果が生じる可能性があります。
ConcurrentHashMapクラスはスレッドセーフです。 したがって、複数のスレッドを1つのオブジェクトで問題なく操作できます。
ConcurrentHashMapでは、読み取り操作は非ブロッキングですが、書き込み操作は特定のセグメントまたはバケットをロックします。デフォルトのバケットまたは同時実行レベルは16です。つまり、16スレッドは、セグメントまたはバケットをロックします。
2.1. ConcurrentModificationException
HashMap のようなオブジェクトの場合、同時操作の実行は許可されていません。 したがって、反復中に HashMap を更新しようとすると、ConcurrentModificationExceptionが発生します。 これは、 synchronizedMap()を使用している場合にも発生します。
@Test(expected = ConcurrentModificationException.class)
public void whenRemoveAndAddOnHashMap_thenConcurrentModificationError() {
Map<Integer, String> map = new HashMap<>();
map.put(1, "baeldung");
map.put(2, "HashMap");
Map<Integer, String> synchronizedMap = Collections.synchronizedMap(map);
Iterator<Entry<Integer, String>> iterator = synchronizedMap.entrySet().iterator();
while (iterator.hasNext()) {
synchronizedMap.put(3, "Modification");
iterator.next();
}
}
ただし、これはConcurrentHashMapには当てはまりません。
Map<Integer, String> map = new ConcurrentHashMap<>();
map.put(1, "baeldung");
map.put(2, "HashMap");
Iterator<Entry<Integer, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
map.put(3, "Modification");
iterator.next()
}
Assert.assertEquals(3, map.size());
2.2. nullサポート
Collections.synchronizedMap()と ConcurrentHashMapはnullキーと値を異なる方法で処理します。
ConcurrentHashMap は、キーまたは値にnullを許可しません。
@Test(expected = NullPointerException.class)
public void allowNullKey_In_ConcurrentHasMap() {
Map<String, Integer> map = new ConcurrentHashMap<>();
map.put(null, 1);
}
でも、 Collections.synchronizedMap()を使用する場合、nullサポートは入力マップに依存します
HashMapに裏打ちされたCollections.synchronizedMap()にnullキーを使用できると主張しましょう。
Map<String, Integer> map = Collections
.synchronizedMap(new HashMap<String, Integer>());
map.put(null, 1);
Assert.assertTrue(map.get(null).equals(1));
同様に、 Collections.synchronizedMap()とConcurrentHashMapの両方の値でnullサポートを検証できます。
3. パフォーマンスの比較
ConcurrentHashMapとCollections.synchronizedMap()。のパフォーマンスを比較してみましょう。この場合、オープンソースフレームワーク Java Microbenchmark Harness ( JMH)からまで、メソッドのパフォーマンスをナノ秒単位で比較します。
これらのマップでランダムな読み取りおよび書き込み操作の比較を実行しました。 JMHベンチマークコードを簡単に見てみましょう。
@Benchmark
public void randomReadAndWriteSynchronizedMap() {
Map<String, Integer> map = Collections.synchronizedMap(new HashMap<String, Integer>());
performReadAndWriteTest(map);
}
@Benchmark
public void randomReadAndWriteConcurrentHashMap() {
Map<String, Integer> map = new ConcurrentHashMap<>();
performReadAndWriteTest(map);
}
private void performReadAndWriteTest(final Map<String, Integer> map) {
for (int i = 0; i < TEST_NO_ITEMS; i++) {
Integer randNumber = (int) Math.ceil(Math.random() * TEST_NO_ITEMS);
map.get(String.valueOf(randNumber));
map.put(String.valueOf(randNumber), randNumber);
}
}
1,000アイテムに対して10スレッドで5回の反復を使用して、パフォーマンスベンチマークを実行しました。 ベンチマークの結果を見てみましょう。
Benchmark Mode Cnt Score Error Units
MapPerformanceComparison.randomReadAndWriteConcurrentHashMap avgt 100 3061555.822 ± 84058.268 ns/op
MapPerformanceComparison.randomReadAndWriteSynchronizedMap avgt 100 3234465.857 ± 60884.889 ns/op
MapPerformanceComparison.randomReadConcurrentHashMap avgt 100 2728614.243 ± 148477.676 ns/op
MapPerformanceComparison.randomReadSynchronizedMap avgt 100 3471147.160 ± 174361.431 ns/op
MapPerformanceComparison.randomWriteConcurrentHashMap avgt 100 3081447.009 ± 69533.465 ns/op
MapPerformanceComparison.randomWriteSynchronizedMap avgt 100 3385768.422 ± 141412.744 ns/op
上記の結果は、ConcurrentHashMapのパフォーマンスが Collections.synchronizedMap()よりも優れていることを示しています。
4. いつ使用するか
データの一貫性が最も重要な場合はCollections.synchronizedMap()を優先し、読み取り操作よりも書き込み操作がはるかに多いパフォーマンスが重要なアプリケーションにはConcurrentHashMapを選択する必要があります。 。
これは、 Collections.synchronizedMap()では、読み取り/書き込み操作の両方で、各スレッドがオブジェクト全体のロックを取得する必要があるためです。 比較すると、 ConcurrentHashMap を使用すると、スレッドはコレクションの個別のセグメントでロックを取得し、同時に変更を加えることができます。
5. 結論
この記事では、 ConcurrentHashMapとCollections.synchronizedMap()の違いを示しました。 また、単純なJMHベンチマークを使用して両方のパフォーマンスを示しました。
いつものように、コードサンプルはGitHubでから入手できます。