web-dev-qa-db-ja.com

ConcurrentHashMapとCollections.synchronizedMap(Map)の違いは何ですか?

複数のスレッドによって同時に変更されるMapがあります。

Java APIには、3つの異なる同期Map実装があるようです。

  • Hashtable
  • Collections.synchronizedMap(Map)
  • ConcurrentHashMap

私が理解していることから、Hashtableは古い実装(廃止されたDictionaryクラスを拡張)であり、後でMapインターフェースに合うように調整されています。同期されていますが、深刻な スケーラビリティの問題を抱えているようです そして新しいプロジェクトにはお勧めできません。

しかし、他の2つはどうですか? Collections.synchronizedMap(Map)ConcurrentHashMapsが返すMapsの違いは何ですか?どちらがどの状況に適していますか?

567
Henning

あなたの必要に応じて、ConcurrentHashMapを使ってください。ブロックすることなく、複数のスレッドからMapを同時に変更することができます。 Collections.synchronizedMap(map)はブロッキングMapを作成します。これは一貫性を保証しますが(適切に使用された場合)、パフォーマンスを低下させます。

データの整合性を確保する必要があり、各スレッドが最新のマップビューを持つ必要がある場合は、2番目のオプションを使用します。パフォーマンスが重要で、各スレッドがデータをマップに挿入するだけで、読み取りの頻度が低い場合は、最初のものを使用します。

401
Yuval Adam
╔═══════════════╦═══════════════════╦═══════════════════╦═════════════════════╗
║   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は/をロックします バケットのみをロックします

223
Sergii Shevchyk

Hashtableの「スケーラビリティの問題」は、Collections.synchronizedMap(Map)とまったく同じように存在します。非常に単純な同期を使用します。つまり、同時に1つのスレッドのみがマップにアクセスできます。

これは、単純な挿入とルックアップがある場合はそれほど問題ではありませんが(極端に集中的に実行しない限り)、マップ全体を反復処理する必要がある場合は大きな問題になります。 1つのスレッドがそれを行い、他のすべてのスレッドは何かを挿入または検索する場合に待機する必要があります。

ConcurrentHashMapは非常に高度な技術を使用して同期の必要性を減らし、同期なしで複数のスレッドによる並列読み取りアクセスを許可します。さらに重要なことは、同期を必要とせず、Mapの変更を許可するIteratorを提供します反復中(反復中に挿入された要素が返されるかどうかは保証されませんが)。

140

ConcurrentHashMapを使用できる場合は、ConcurrentHashMapをお勧めします。ただし、少なくともJava 5が必要です。

それは複数のスレッドによって使用されるときうまくスケーリングするように設計されています。一度に1つのスレッドだけがMapにアクセスするとパフォーマンスがわずかに低下することがありますが、複数のスレッドが同時にMapにアクセスすると大幅に向上します。

blogエントリ が優れた本 Java Concurrency In Practice からの表を再現していることを私は完全にお勧めします。

Collections.synchronizedMapが本当に意味を成すのは、TreeMapのように、マップを他の特性(おそらくある種の順序付きマップ)でラップする必要がある場合だけです。

33
Bill Michell

これら2つの主な違いは、ConcurrentHashMapは更新中のデータの一部だけをロックし、他の部分のデータは他のスレッドからアクセスできることです。ただし、Collections.synchronizedMap()は更新中にすべてのデータをロックします。他のスレッドはロックが解除されたときにのみデータにアクセスできます。多くの更新操作と比較的少量の読み取り操作がある場合は、ConcurrentHashMapを選択してください。

また、ConcurrentHashMapは、渡されたMap内の要素の順序を保持しないという点もあります。データを格納する場合のHashMapと似ています。要素の順序が保持されるという保証はありません。 Collections.synchronizedMap()は渡されたMapの要素の順序を保持しますが、たとえばTreeMapConcurrentHashMapに渡すと、ConcurrentHashMapの要素の順序はTreeMapの順序と同じにはなりませんが、Collections.synchronizedMap()は順序を保持します。

さらに、ConcurrentHashMapは、1つのスレッドがマップを更新していて、別のスレッドがマップから取得したイテレータをトラバースしている間は、ConcurrentModificationExceptionがスローされないことを保証できます。しかし、Collections.synchronizedMap()はこれに関して保証されていません。

one post はこれら2つの違いとConcurrentSkipListMapを示しています。

32
PixelsTech

ConcurrentHashMapでは、ロックはMap全体ではなくセグメントに適用されます。各セグメントは独自の内部ハッシュテーブルを管理します。ロックは更新操作にのみ適用されます。 Collections.synchronizedMap(Map)はマップ全体を同期させます。

12
Satish

いつものように、並行性 - オーバーヘッド - スピードのトレードオフが関係しています。あなたは本当に決定を下すためにあなたのアプリケーションの詳細な同時実行要件を考慮する必要があり、それからそれが十分に良いかどうか確かめるためにあなたのコードをテストする必要があります。

11
Zach Scrivena

同期マップ:

同期マップも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キーを許可します。

類似リンク

Link1

Link2

パフォーマンス比較

9

あなたは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 の記事を読んでください。

9
eljenso

ここにいくつかあります:

1)ConcurrentHashMapはMapの一部だけをロックしますが、SynchronizedMapはMAp全体をロックします。
2)ConcurrentHashMapはSynchronizedMapよりも優れたパフォーマンスを持ち、よりスケーラブルです。
3)マルチリーダーおよびシングルライターの場合は、ConcurrentHashMapが最適です。

このテキストの由来は JavaのConcurrentHashMapとハッシュテーブルの違い

7
Raj

ConcurrentHashMapとsynchronisedHashmapとHashtableを使用することで、スレッドの安全性を実現できます。しかし、それらのアーキテクチャーを見れば、大きな違いがあります。

  1. synchronisedHashmapとHashtable

どちらもオブジェクトレベルでロックを維持します。したがって、put/getのような操作を実行したい場合は、まずロックを取得する必要があります。同時に、他のスレッドはいかなる操作も実行できません。そのため、一度に1つのスレッドしか操作できません。だからここで待ち時間が増えます。 ConcurrentHashMapと比べると、パフォーマンスは比較的低いと言えます。

  1. ConcurrentHashMap

ロックはセグメントレベルで維持されます。これは16のセグメントを持ち、デフォルトで16の並行性レベルを維持します。そのため、一度に16のスレッドがConcurrentHashMap上で動作できるようになります。さらに、読み取り操作はロックを必要としません。したがって、任意の数のスレッドがそれに対してget操作を実行できます。

Thread1がセグメント2でput操作を実行し、thread2がセグメント4でput操作を実行したい場合は、ここで許可されます。つまり、16のスレッドがConcurrentHashMapに対して一度に更新(put/delete)操作を実行できます。

だから待ち時間はここでより少なくなります。そのため、パフォーマンスはsynchronisedHashmapやHashtableよりも比較的優れています。

7
Sumanth Varada

ConcurrentHashMap

  • プロジェクトで非常に高い並行性が必要な場合は、ConcurrentHashMapを使用してください。
  • マップ全体を同期させずにスレッドセーフです。
  • ロック付きで書き込みが行われている間、読み取りは非常に速く発生する可能性があります。
  • オブジェクトレベルでのロックはありません。
  • ロックは、ハッシュマップバケットレベルでははるかに細かい粒度になります。
  • あるスレッドが別のスレッドがそれを反復処理している間に変更しようとした場合、ConcurrentHashMapはConcurrentModificationExceptionをスローしません。
  • ConcurrentHashMapは多数のロックを使用します。

SynchronizedHashMap

  • オブジェクトレベルでの同期.
  • すべての読み取り/書き込み操作でロックを取得する必要があります。
  • コレクション全体をロックすることはパフォーマンスのオーバーヘッドです。
  • これは基本的に、マップ全体への1つのスレッドだけへのアクセスを与え、他のすべてのスレッドをブロックします。
  • 競合が発生する可能性があります。
  • SynchronizedHashMapはIteratorを返します。これは同時変更で失敗します。

ソース

5
Premraj

ConcurrentHashMapは同時アクセス用に最適化されています。

アクセスはマップ全体をロックするのではなく、よりきめの細かい戦略を使用します。これによりスケーラビリティが向上します。特に同時アクセスのための機能強化もある。並行イテレータ.

4
starblue

がある 1つの重要な機能 それが提供する並行処理機能以外のConcurrentHashMapについて注意するために、それは フェイルセーフ iteratorです。私は、開発者がエントリセットを編集したいという理由だけでConcurrentHashMapを使っているのを見ました。 Collections.synchronizedMap(Map) fail-safe iteratorを提供しませんが、それは提供します フェイルファースト 代わりにイテレータ。超高速反復子は、反復中に編集できないマップのサイズのスナップショットを使用します。

3
hi.nitish
  1. データの整合性が非常に重要な場合 - HashtableまたはCollections.synchronizedMap(Map)を使用してください。
  2. 速度やパフォーマンスが非常に重要であり、データ更新が危険にさらされる可能性がある場合は、ConcurrentHashMapを使用してください。
3
Shivam Maharshi

Collections.synchronizedMap()メソッドはHashMapのすべてのメソッドを同期させ、すべてのメソッドを共通のロックでロックするため、一度に1つのスレッドが入ることができるデータ構造に効果的に縮小します。

ConcurrentHashMapでは、同期は少し異なります。すべてのメソッドを共通のロックでロックするのではなく、ConcurrentHashMapは別々のバケットに対して別々のロックを使用するため、Mapの一部のみをロックします。デフォルトでは16個のバケットがあり、別々のバケットには別々のロックもあります。したがって、デフォルトの並行性レベルは16です。つまり、理論上は、16個のスレッドがすべて別々のバケットに移動する場合、16個のスレッドがConcurrentHashMapにアクセスできるということです。

1
infoj

一般的に、もしあなたがConcurrentHashMapを使いたいのであれば、 'updates'を見逃す準備ができていることを確認してください。
(つまり、HashMapの内容を印刷しても最新のMapが印刷されることは保証されません)そしてCyclicBarrierのようなAPIを使用して、プログラムのライフサイクル全体で一貫性を保ちます。

1
Kounavi

提案されたものの他に、私は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オブジェクトの主メソッドに単一のロックを追加することとして要約できます。ロックによって保護されているすべてのメソッドに、同時に複数のスレッドからアクセスすることはできません。つまり、putgetのような通常の操作は、Mapオブジェクト内のすべてのデータに対して単一スレッドで同時に実行できます。

これはMapオブジェクトを安全にスレッド化しますが、場合によってはパフォーマンスが問題になるかもしれません。

ConcurrentMapは実装上はるかに複雑です。詳細については より良いHashMapの構築 を参照してください。一言で言えば、スレッドセーフとパフォーマンスの両方を考慮して実装されています。

0
Gearon