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


607

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

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

  • Hashtable
  • Collections.synchronizedMap(Map)
  • ConcurrentHashMap

私が理解していることから、インターフェースに合うように後で適応されHashtableた古い実装(廃止されたDictionaryクラスを拡張)Mapです。それは一方でされた同期、深刻な持っているようだスケーラビリティの問題を、新たなプロジェクトのために推奨されません。

しかし、他の2つはどうですか?返された地図との違いは何ですCollections.synchronizedMap(Map)ConcurrentHashMapSは?どちらがどの状況に適していますか?


7
@SmilesinaJarリンクは現在壊れています。これはこの記事のアーカイブされたコピーです:ConcurrentHashMapがHashtableより優れており、HashMapと同じくらい優れている理由
informatik01

2
IBM:ConcurrentHashMapがスレッドの安全性を損なうことなくより高い並行性を提供する方法@ ibm.com
developerworks/java/library/j

参考までに、Java 6はConcurrentSkipListMap別のスレッドセーフなMap実装として導入されました。スキップリストアルゴリズムを使用して、負荷がかかった状態での同時性が高くなるように設計されています。
バジルブルク

回答:


423

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

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


8
ソースコードを見ると、同期マップは1つのミューテックス(ブロッキング)を備えた実装にすぎませんが、ConcurrentHashMapは同時アクセスを処理するのがより複雑です
Vinze

123
また、ConcurrentHashMapはnullキーまたは値を許可しないことにも注意してください。したがって、それらは同期マップの代替案とは等しくありません。
onejigtwojig

24
私はあなたがこれを読むべきだと思いますhttp://ria101.wordpress.com/2011/12/12/concurrenthashmap-avoid-a-common-misuse/
スパーク氏

5
その記事で提起された@AbdullahShaikh問題は、Java 7に固定されており、更なる改善は、Java 8で行われている
pulse0ne

5
@hengxin:マップの複数のクエリまたは更新で構成される操作を実行しているとき、またはマップを反復しているときは常に、整合性を確保するためにマップを手動で同期する必要があります。同期されたマップは、マップ上の単一の操作(メソッドの呼び出し)に対してのみ一貫性を保証します。これは、ほとんどの実際の操作が重要なため、とにかく手動で同期する必要があるため、多くの場合役に立たなくなります。
Holger

241
╔═══════════════╦═══════════════════╦═══════════════════╦═════════════════════╗
║   Property    ║     HashMap       ║    Hashtable      ║  ConcurrentHashMap  ║
╠═══════════════╬═══════════════════╬═══════════════════╩═════════════════════╣ 
║      Null     ║     allowed       ║              not allowed                ║
║  values/keys  ║                   ║                                         ║
╠═══════════════╬═══════════════════╬═════════════════════════════════════════╣
║ Thread-safety ║                   ║                                         ║
║   features    ║       no          ║                  yes                    ║
╠═══════════════╬═══════════════════╬═══════════════════╦═════════════════════╣
║     Lock      ║       not         ║ locks the whole   ║ locks the portion   ║        
║  mechanism    ║    applicable     ║       map         ║                     ║ 
╠═══════════════╬═══════════════════╩═══════════════════╬═════════════════════╣
║   Iterator    ║               fail-fast               ║ weakly consistent   ║ 
╚═══════════════╩═══════════════════════════════════════╩═════════════════════╝

ロックメカニズムについて: Hashtable オブジェクトConcurrentHashMapロックし、バケットのみをロックします


13
Hashtableマップの一部をロックしていません。実装を見てください。synchronizedロックなしのキーを使用しているため、基本的にはhashtable操作ごとに全体がロックされます。
RMachnik、2015年

6
synchronizedMapについてはどうですか?
Samuel Edwin Ward

3
すべてのメソッドは、スレッドセーフです除きCollections.syncronizedMapの動作は、バッキング・マップのようなものです
セルギShevchyk

5
テーブルを印刷して、それぞれ5ドルで販売します;)。グッドワン@shevchyk
realPK

編集:どちらも完全にスレッドセーフではありません。これは、新しい開発者には少し誤解を招く可能性があります。ibm.com/developerworks/java/library/j-jtp07233/index.htmlを参照して、ConcurrentHashMapでさえ外部のデータ競合から完全にスレッドセーフではないことを理解してください。(例:1つのスレッドが値を削除し、別のスレッドがそれが存在するかどうかを確認し、存在しない場合はそれを配置しようとします。これはデータ競合状態であり、 "ConcurrentHashMap"を使用しても、すべてのスレッドの安全性の問題が軽減されないことを意味します。
ゾンビ

142

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

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

ConcurrentHashMapもっと重要なのは、同期の必要性を低減し、同期することなく、複数のスレッドによる並列読み出しアクセスを許可するとするために使用する、非常に高度な技術、提供してIterator何の同期を必要とせず、それが保証かどうかを行うものではありませんけれどもさえ(地図interation中に変更することを可能にすることを反復中に挿入された要素は返されません)。


4
今それが私が欲しかったものです!:)同期されていないイテレータは、純粋な甘さだけです!情報をありがとう!:)(:
Kounavi 2011年

すばらしい答えですが、読み取りスレッドが同期していないため、取得スレッドは最新の更新を取得しません。
MrAは2013年

@MrA:ConcurrentHashMapについて質問していますか?そして、「検索」とはどういう意味ですか?
Michael Borgwardt 2013年

4
@Michael BorgwardtのConcurrentHashmapなど。複数のスレッドがあるとします。それらの一部はマップを更新し、一部は同じマップからデータを取得しています。スレッドが読み取ろうとしているこのシナリオでのSOは、リーダースレッドがロックを保持する必要がないため、更新された最新のデータを取得することが保証されます。
MrA 2013年

35

使用できる場合はConcurrentHashMapが推奨されますが、少なくともJava 5が必要です。

これは、複数のスレッドで使用されるときに適切にスケーリングするように設計されています。一度に1つのスレッドのみがマップにアクセスする場合、パフォーマンスはわずかに低下しますが、複数のスレッドが同時にマップにアクセスする場合は、パフォーマンスが大幅に向上します。

優れた書籍「Java Concurrency In Practice」の表を再現したブログエントリを見つけました

Collections.synchronizedMapが実際に意味を持つのは、他の特性を持つマップをラップする必要がある場合のみです。おそらく、TreeMapのような、並べ替えられたマップのようなものです。


2
ええ-私は他のすべての答えでその本について言及しているようです!
Bill Michell、2012年

@BillMichellリンクが壊れている

@Govindaリンクにアクセスする前にJavaScriptをオフにしてください。ブログエントリはまだあります!
ビルミシェル2018年

32

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

もう1つの違いは、ConcurrentHashMap渡されたマップ内の要素の順序が保持されないことです。これはHashMap、データを保存する場合と同様です。要素の順序が保持される保証はありません。一方でCollections.synchronizedMap()保持されます地図の要素の順序は、渡された。たとえば、あなたが渡した場合TreeMapConcurrentHashMap、内の要素の順序ConcurrentHashMapで順序と同じではないかもしれないがTreeMap、しかし、Collections.synchronizedMap()順序を維持します。

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

これら2つの違いとを示す1つの投稿がありConcurrentSkipListMapます。


13

同期マップ:

Synchronized MapもHashtableと大差なく、並行Javaプログラムで同様のパフォーマンスを提供します。HashtableとSynchronizedMapの唯一の違いは、SynchronizedMapはレガシーではなく、Collections.synchronizedMap()メソッドを使用して、マップをラップして同期バージョンを作成できることです。

ConcurrentHashMap:

ConcurrentHashMapクラスは、標準のHashMapの並行バージョンを提供します。これは、Collectionsクラスで提供される同期マップ機能の改善です。

ハッシュテーブルや同期マップとは異なり、マップ全体をロックするのではなく、マップをセグメントに分割し、それらに対してロックを行います。リーダースレッドの数がライタースレッドの数よりも多い場合、パフォーマンスは向上します。

デフォルトでは、ConcurrentHashMapは16の領域に分割され、ロックが適用されます。このデフォルト数は、ConcurrentHashMapインスタンスの初期化中に設定できます。特定のセグメントにデータを設定すると、そのセグメントのロックが取得されます。つまり、2つの更新が別々のバケットに影響を与える場合でも、2つの更新を同時に安全に実行できるため、ロックの競合が最小限に抑えられ、パフォーマンスが最大化されます。

ConcurrentHashMapがConcurrentModificationExceptionをスローしない

別のスレッドが反復している間に1つのスレッドが変更を試みても、ConcurrentHashMapはConcurrentModificationExceptionをスローしません。

synchornizedMapとConcurrentHashMapの違い

Collections.synchornizedMap(HashMap)は、Hashtableとほぼ同等のコレクションを返します。ConcurrentHashMapの場合は、並行処理レベルに基づいてMap全体を異なるパーティションに分割することにより、Mapに対するすべての変更操作がMapオブジェクトでロックされます。マップ全体をロックするのではなく、特定の部分のみをロックします。

ConcurrentHashMapではnullキーまたはnull値は許可されませんが、同期されたHashMapでは1つのnullキーが許可されます。

同様のリンク

リンク1

リンク2

性能比較


12

いつものように、並行性(オーバーヘッド)と速度のトレードオフが関係しています。アプリケーションの詳細な同時実行性要件を検討して決定を下し、コードをテストして十分かどうかを確認する必要があります。


12

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


あなたは見てくださいstackoverflow.com/questions/48579060/…
gstackoverflow

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するの単純な代替案ではないと考えてください。この記事を読んでその複雑さをよりよく理解してください。HashMapsynchronized


7

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

1)ConcurrentHashMapはMapの一部のみをロックしますが、SynchronizedMapはMAp全体をロックします。
2)ConcurrentHashMapはSynchronizedMapよりもパフォーマンスが高く、よりスケーラブルです。
3)複数のリーダーと単一のライターの場合、ConcurrentHashMapが最適です。

このテキストは、JavaのConcurrentHashMapとハッシュテーブルの違いからのものです


7

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

  1. synchronisedHashmapおよびHashtable

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

  1. ConcurrentHashMap

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

スレッド1がセグメント2で書き込み操作を実行し、スレッド2がセグメント4で書き込み操作を実行する場合、ここで許可されます。つまり、16個のスレッドが一度にConcurrentHashMapに対して更新(書き込み/削除)操作を実行できます。

待ち時間が少なくなるように。したがって、パフォーマンスはsynchronisedHashmapおよびHashtableよりも比較的優れています。


1
、1。複数のスレッドが同じブロックを編集しようとするとどうなりますか?2. 2つのスレッドが同じブロックからデータを読み取ろうとすると、別のスレッドが同時にデータを書き込む場合はどうなりますか?
prnjn

6

ConcurrentHashMap

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

SynchronizedHashMap

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

ソース


4

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

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


4

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


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

2

一般に、ConcurrentHashMap「更新」を見逃す準備ができていることを確認する場合
(つまり、HashMapの内容を印刷しても、最新のマップが印刷されることは保証されません)CyclicBarrier、プログラム全体の一貫性を確保するようなAPIを使用します。ライフサイクル。


1

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

ConcurrentHashMapでは、同期は少し異なります。すべてのメソッドを共通のロックでロックするのではなく、ConcurrentHashMapは個別のバケットに個別のロックを使用するため、マップの一部のみをロックします。デフォルトでは、16のバケットと、個別のバケット用の個別のロックがあります。つまり、デフォルトの同時実行レベルは16です。つまり、理論的には、16のスレッドがConcurrentHashMapにアクセスできるのは、それらすべてがバケットを分離する場合です。


1

ConcurrentHashMapは、同時実行パッケージの一部として、Java 1.5のHashtableの代替として提示されました。ConcurrentHashMapを使用すると、並行マルチスレッド環境で安全に使用できるかどうかだけでなく、HashtableやsynchronizedMapよりも優れたパフォーマンスを提供できます。ConcurrentHashMapはMapの一部をロックするため、パフォーマンスが向上します。同時読み取り操作を可能にすると同時に、書き込み操作を同期することで整合性を維持します。

ConcurrentHashMapの実装方法

ConcurrentHashMapはHashtableの代替として開発されたもので、Hashtableのすべての機能をサポートします。ConcurrentHashMapを使用すると、複数のリーダーがブロックを使用せずに同時に読み取ることができます。Mapを別の部分に分離し、更新でMapの一部のみをブロックすることで可能になります。デフォルトでは、同時実行レベルは16であるため、マップは16の部分に分割され、各部分は個別のブロックによって管理されます。つまり、16個のスレッドがMapの異なる部分を処理する場合、同時に16個のスレッドがMapを処理することができます。これにより、ConcurrentHashMapの生産性が高まり、スレッドの安全性が低下しません。

ConcurrentHashMapのいくつかの重要な機能に興味があり、このMapの実現を使用する必要がある場合-良い記事へのリンクを配置するだけ-JavaでConcurrentHashMapを使用する方法


0

提案されていることに加えて、関連するソースコードを投稿したいと思います SynchronizedMapます。

Mapスレッドを安全にするために、Collections.synchronizedMapステートメントをて、パラメーターとしてマップインスタンスを入力ます。

synchronizedMapin の実装は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の構築を参照してください。簡単に言うと、スレッドセーフとパフォーマンスの両方を考慮して実装されています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.