複数のスレッドによって同時に変更されるMapがあります。
Java APIには、3つの異なる同期Map実装があるようです。
Hashtable
Collections.synchronizedMap(Map)
ConcurrentHashMap
私が理解していることから、Hashtable
は古い実装(廃止されたDictionary
クラスを拡張)であり、後でMap
インターフェースに合うように調整されています。 は 同期されていますが、深刻な スケーラビリティの問題を抱えているようです そして新しいプロジェクトにはお勧めできません。
しかし、他の2つはどうですか? Collections.synchronizedMap(Map)
とConcurrentHashMap
sが返すMapsの違いは何ですか?どちらがどの状況に適していますか?
あなたの必要に応じて、ConcurrentHashMap
を使ってください。ブロックすることなく、複数のスレッドからMapを同時に変更することができます。 Collections.synchronizedMap(map)
はブロッキングMapを作成します。これは一貫性を保証しますが(適切に使用された場合)、パフォーマンスを低下させます。
データの整合性を確保する必要があり、各スレッドが最新のマップビューを持つ必要がある場合は、2番目のオプションを使用します。パフォーマンスが重要で、各スレッドがデータをマップに挿入するだけで、読み取りの頻度が低い場合は、最初のものを使用します。
╔═══════════════╦═══════════════════╦═══════════════════╦═════════════════════╗
║ Property ║ HashMap ║ Hashtable ║ ConcurrentHashMap ║
╠═══════════════╬═══════════════════╬═══════════════════╩═════════════════════╣
║ Null ║ allowed ║ not allowed ║
║ values/keys ║ ║ ║
╠═══════════════╬═══════════════════╬═════════════════════════════════════════╣
║Is thread-safe ║ no ║ yes ║
╠═══════════════╬═══════════════════╬═══════════════════╦═════════════════════╣
║ Lock ║ not ║ locks the whole ║ locks the portion ║
║ mechanism ║ applicable ║ map ║ ║
╠═══════════════╬═══════════════════╩═══════════════════╬═════════════════════╣
║ Iterator ║ fail-fast ║ weakly consistent ║
╚═══════════════╩═══════════════════════════════════════╩═════════════════════╝
ロックの仕組みについて:Hashtable
はオブジェクトをロックします 、ConcurrentHashMap
は/をロックします バケットのみをロックします 。
Hashtable
の「スケーラビリティの問題」は、Collections.synchronizedMap(Map)
とまったく同じように存在します。非常に単純な同期を使用します。つまり、同時に1つのスレッドのみがマップにアクセスできます。
これは、単純な挿入とルックアップがある場合はそれほど問題ではありませんが(極端に集中的に実行しない限り)、マップ全体を反復処理する必要がある場合は大きな問題になります。 1つのスレッドがそれを行い、他のすべてのスレッドは何かを挿入または検索する場合に待機する必要があります。
ConcurrentHashMap
は非常に高度な技術を使用して同期の必要性を減らし、同期なしで複数のスレッドによる並列読み取りアクセスを許可します。さらに重要なことは、同期を必要とせず、Mapの変更を許可するIterator
を提供します反復中(反復中に挿入された要素が返されるかどうかは保証されませんが)。
ConcurrentHashMapを使用できる場合は、ConcurrentHashMapをお勧めします。ただし、少なくともJava 5が必要です。
それは複数のスレッドによって使用されるときうまくスケーリングするように設計されています。一度に1つのスレッドだけがMapにアクセスするとパフォーマンスがわずかに低下することがありますが、複数のスレッドが同時にMapにアクセスすると大幅に向上します。
blogエントリ が優れた本 Java Concurrency In Practice からの表を再現していることを私は完全にお勧めします。
Collections.synchronizedMapが本当に意味を成すのは、TreeMapのように、マップを他の特性(おそらくある種の順序付きマップ)でラップする必要がある場合だけです。
これら2つの主な違いは、ConcurrentHashMap
は更新中のデータの一部だけをロックし、他の部分のデータは他のスレッドからアクセスできることです。ただし、Collections.synchronizedMap()
は更新中にすべてのデータをロックします。他のスレッドはロックが解除されたときにのみデータにアクセスできます。多くの更新操作と比較的少量の読み取り操作がある場合は、ConcurrentHashMap
を選択してください。
また、ConcurrentHashMap
は、渡されたMap内の要素の順序を保持しないという点もあります。データを格納する場合のHashMap
と似ています。要素の順序が保持されるという保証はありません。 Collections.synchronizedMap()
は渡されたMapの要素の順序を保持しますが、たとえばTreeMap
をConcurrentHashMap
に渡すと、ConcurrentHashMap
の要素の順序はTreeMap
の順序と同じにはなりませんが、Collections.synchronizedMap()
は順序を保持します。
さらに、ConcurrentHashMap
は、1つのスレッドがマップを更新していて、別のスレッドがマップから取得したイテレータをトラバースしている間は、ConcurrentModificationException
がスローされないことを保証できます。しかし、Collections.synchronizedMap()
はこれに関して保証されていません。
one post はこれら2つの違いとConcurrentSkipListMap
を示しています。
ConcurrentHashMap
では、ロックはMap全体ではなくセグメントに適用されます。各セグメントは独自の内部ハッシュテーブルを管理します。ロックは更新操作にのみ適用されます。 Collections.synchronizedMap(Map)
はマップ全体を同期させます。
Hashtable
および ConcurrentHashMap
は、null
キーまたはnull
値を許可しません。
Collections.synchronizedMap(Map)
同期 all 操作(get
、put
、size
など)。
ConcurrentHashMap
は検索の完全な並行性、および更新時の調整可能な予測並行性をサポートします。
いつものように、並行性 - オーバーヘッド - スピードのトレードオフが関係しています。あなたは本当に決定を下すためにあなたのアプリケーションの詳細な同時実行要件を考慮する必要があり、それからそれが十分に良いかどうか確かめるためにあなたのコードをテストする必要があります。
同期マップ:
同期マップもHashtableとそれほど違いはなく、並行Javaプログラムでも同様のパフォーマンスを提供します。 HashtableとSynchronizedMapの唯一の違いは、SynchronizedMapはレガシーではないということです。また、Collections.synchronizedMap()メソッドを使用して、任意のMapをラップして同期バージョンを作成できます。
ConcurrentHashMap:
ConcurrentHashMapクラスは、標準のHashMapの並行バージョンを提供します。これは、Collectionsクラスで提供されるsynchronizedMap機能の改善です。
HashtableとSynchronized Mapとは異なり、Map全体をロックするのではなく、代わりにマップをセグメントに分割し、それらをロックします。リーダースレッドの数がライタースレッドの数より多い場合は、パフォーマンスが向上します。
ConcurrentHashMapはデフォルトで16の領域に分けられ、ロックが適用されます。このデフォルト数は、ConcurrentHashMapインスタンスの初期化中に設定できます。特定のセグメントにデータを設定すると、そのセグメントのロックが取得されます。つまり、2つの更新がそれぞれ別々のバケットに影響を与えても、2つの更新が同時に安全に実行されるため、ロックの競合が最小限に抑えられ、パフォーマンスが最大化されます。
ConcurrentHashMapはConcurrentModificationExceptionをスローしません
あるスレッドが別のスレッドがそれを繰り返し処理している間に変更しようとしても、ConcurrentHashMapはConcurrentModificationExceptionをスローしません。
synchornizedMapとConcurrentHashMapの違い
Collections.synchornizedMap(HashMap)は、Hashtableとほぼ同等のコレクションを返します。ConcurrentHashMapの場合、Mapに対するすべての変更操作は、並行性レベルに基づいてMap全体を異なるパーティションに分割することによって実現されますMap全体をロックするのではなく、特定の部分だけをロックします。
ConcurrentHashMapはNULLキーまたはNULL値を許可しませんが、同期HashMapは1つのNULLキーを許可します。
類似リンク
あなたはHashTable
については正しいです、あなたはそれについて忘れることができます。
あなたの記事 は、HashTableと同期化されたラッパークラスは一度に1つのスレッドにしかマップへのアクセスを許可しないことで基本的なスレッドセーフを提供しますがまだ追加の同期が必要です、例えば:
synchronized (records) {
Record rec = records.get(id);
if (rec == null) {
rec = new Record(id);
records.put(id, rec);
}
return rec;
}
ただし、ConcurrentHashMap
が、上に示すように一般的なHashMap
ブロックを持つsynchronized
の単純な代替方法であるとは思わないでください。その複雑さをよりよく理解するために this の記事を読んでください。
ここにいくつかあります:
1)ConcurrentHashMapはMapの一部だけをロックしますが、SynchronizedMapはMAp全体をロックします。
2)ConcurrentHashMapはSynchronizedMapよりも優れたパフォーマンスを持ち、よりスケーラブルです。
3)マルチリーダーおよびシングルライターの場合は、ConcurrentHashMapが最適です。
このテキストの由来は JavaのConcurrentHashMapとハッシュテーブルの違い
ConcurrentHashMapとsynchronisedHashmapとHashtableを使用することで、スレッドの安全性を実現できます。しかし、それらのアーキテクチャーを見れば、大きな違いがあります。
どちらもオブジェクトレベルでロックを維持します。したがって、put/getのような操作を実行したい場合は、まずロックを取得する必要があります。同時に、他のスレッドはいかなる操作も実行できません。そのため、一度に1つのスレッドしか操作できません。だからここで待ち時間が増えます。 ConcurrentHashMapと比べると、パフォーマンスは比較的低いと言えます。
ロックはセグメントレベルで維持されます。これは16のセグメントを持ち、デフォルトで16の並行性レベルを維持します。そのため、一度に16のスレッドがConcurrentHashMap上で動作できるようになります。さらに、読み取り操作はロックを必要としません。したがって、任意の数のスレッドがそれに対してget操作を実行できます。
Thread1がセグメント2でput操作を実行し、thread2がセグメント4でput操作を実行したい場合は、ここで許可されます。つまり、16のスレッドがConcurrentHashMapに対して一度に更新(put/delete)操作を実行できます。
だから待ち時間はここでより少なくなります。そのため、パフォーマンスはsynchronisedHashmapやHashtableよりも比較的優れています。
ConcurrentHashMap
SynchronizedHashMap
ConcurrentHashMapは同時アクセス用に最適化されています。
アクセスはマップ全体をロックするのではなく、よりきめの細かい戦略を使用します。これによりスケーラビリティが向上します。特に同時アクセスのための機能強化もある。並行イテレータ.
がある 1つの重要な機能 それが提供する並行処理機能以外のConcurrentHashMap
について注意するために、それは フェイルセーフ iteratorです。私は、開発者がエントリセットを編集したいという理由だけでConcurrentHashMap
を使っているのを見ました。 Collections.synchronizedMap(Map)
は fail-safe iteratorを提供しませんが、それは提供します フェイルファースト 代わりにイテレータ。超高速反復子は、反復中に編集できないマップのサイズのスナップショットを使用します。
Collections.synchronizedMap()メソッドはHashMapのすべてのメソッドを同期させ、すべてのメソッドを共通のロックでロックするため、一度に1つのスレッドが入ることができるデータ構造に効果的に縮小します。
ConcurrentHashMapでは、同期は少し異なります。すべてのメソッドを共通のロックでロックするのではなく、ConcurrentHashMapは別々のバケットに対して別々のロックを使用するため、Mapの一部のみをロックします。デフォルトでは16個のバケットがあり、別々のバケットには別々のロックもあります。したがって、デフォルトの並行性レベルは16です。つまり、理論上は、16個のスレッドがすべて別々のバケットに移動する場合、16個のスレッドがConcurrentHashMapにアクセスできるということです。
一般的に、もしあなたがConcurrentHashMap
を使いたいのであれば、 'updates'を見逃す準備ができていることを確認してください。
(つまり、HashMapの内容を印刷しても最新のMapが印刷されることは保証されません)そしてCyclicBarrier
のようなAPIを使用して、プログラムのライフサイクル全体で一貫性を保ちます。
提案されたものの他に、私はSynchronizedMap
に関連したソースコードを投稿したいです。
Map
スレッドを安全にするために、Collections.synchronizedMap
ステートメントを使い、マップインスタンスをパラメータとして入力することができます。
synchronizedMap
におけるCollections
の実装は以下のようになります。
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
return new SynchronizedMap<>(m);
}
ご覧のとおり、入力Map
オブジェクトはSynchronizedMap
オブジェクトによってラップされています。SynchronizedMap
の実装を掘り下げましょう。
private static class SynchronizedMap<K,V>
implements Map<K,V>, Serializable {
private static final long serialVersionUID = 1978198479659022715L;
private final Map<K,V> m; // Backing Map
final Object mutex; // Object on which to synchronize
SynchronizedMap(Map<K,V> m) {
this.m = Objects.requireNonNull(m);
mutex = this;
}
SynchronizedMap(Map<K,V> m, Object mutex) {
this.m = m;
this.mutex = mutex;
}
public int size() {
synchronized (mutex) {return m.size();}
}
public boolean isEmpty() {
synchronized (mutex) {return m.isEmpty();}
}
public boolean containsKey(Object key) {
synchronized (mutex) {return m.containsKey(key);}
}
public boolean containsValue(Object value) {
synchronized (mutex) {return m.containsValue(value);}
}
public V get(Object key) {
synchronized (mutex) {return m.get(key);}
}
public V put(K key, V value) {
synchronized (mutex) {return m.put(key, value);}
}
public V remove(Object key) {
synchronized (mutex) {return m.remove(key);}
}
public void putAll(Map<? extends K, ? extends V> map) {
synchronized (mutex) {m.putAll(map);}
}
public void clear() {
synchronized (mutex) {m.clear();}
}
private transient Set<K> keySet;
private transient Set<Map.Entry<K,V>> entrySet;
private transient Collection<V> values;
public Set<K> keySet() {
synchronized (mutex) {
if (keySet==null)
keySet = new SynchronizedSet<>(m.keySet(), mutex);
return keySet;
}
}
public Set<Map.Entry<K,V>> entrySet() {
synchronized (mutex) {
if (entrySet==null)
entrySet = new SynchronizedSet<>(m.entrySet(), mutex);
return entrySet;
}
}
public Collection<V> values() {
synchronized (mutex) {
if (values==null)
values = new SynchronizedCollection<>(m.values(), mutex);
return values;
}
}
public boolean equals(Object o) {
if (this == o)
return true;
synchronized (mutex) {return m.equals(o);}
}
public int hashCode() {
synchronized (mutex) {return m.hashCode();}
}
public String toString() {
synchronized (mutex) {return m.toString();}
}
// Override default methods in Map
@Override
public V getOrDefault(Object k, V defaultValue) {
synchronized (mutex) {return m.getOrDefault(k, defaultValue);}
}
@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
synchronized (mutex) {m.forEach(action);}
}
@Override
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
synchronized (mutex) {m.replaceAll(function);}
}
@Override
public V putIfAbsent(K key, V value) {
synchronized (mutex) {return m.putIfAbsent(key, value);}
}
@Override
public boolean remove(Object key, Object value) {
synchronized (mutex) {return m.remove(key, value);}
}
@Override
public boolean replace(K key, V oldValue, V newValue) {
synchronized (mutex) {return m.replace(key, oldValue, newValue);}
}
@Override
public V replace(K key, V value) {
synchronized (mutex) {return m.replace(key, value);}
}
@Override
public V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
synchronized (mutex) {return m.computeIfAbsent(key, mappingFunction);}
}
@Override
public V computeIfPresent(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
synchronized (mutex) {return m.computeIfPresent(key, remappingFunction);}
}
@Override
public V compute(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
synchronized (mutex) {return m.compute(key, remappingFunction);}
}
@Override
public V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
synchronized (mutex) {return m.merge(key, value, remappingFunction);}
}
private void writeObject(ObjectOutputStream s) throws IOException {
synchronized (mutex) {s.defaultWriteObject();}
}
}
SynchronizedMap
が行うことは、入力Map
オブジェクトの主メソッドに単一のロックを追加することとして要約できます。ロックによって保護されているすべてのメソッドに、同時に複数のスレッドからアクセスすることはできません。つまり、put
やget
のような通常の操作は、Map
オブジェクト内のすべてのデータに対して単一スレッドで同時に実行できます。
これはMap
オブジェクトを安全にスレッド化しますが、場合によってはパフォーマンスが問題になるかもしれません。
ConcurrentMap
は実装上はるかに複雑です。詳細については より良いHashMapの構築 を参照してください。一言で言えば、スレッドセーフとパフォーマンスの両方を考慮して実装されています。