156

マップが構築される場合があり、一度初期化すると二度と変更されません。ただし、複数のスレッドから (get(key) のみを介して) アクセスされます。このように a を使用しても安全java.util.HashMapですか?

(現在、私は を喜んで使用しておりjava.util.concurrent.ConcurrentHashMap、パフォーマンスを向上させる必要性は測定されていませんが、単純なもので十分かどうか単純に興味がありますHashMap。したがって、この質問はどちらを使用すべきか?」ではなく、パフォーマンスの問題でもありません。むしろ、問題は「安全かどうか」です)

4

12 に答える 12

70

Java メモリ モデルの神である Jeremy Manson は、このトピックに関する 3 部構成のブログを持っています。本質的には、「不変の HashMap にアクセスしても安全か」という質問をしているからです。その答えはイエスです。しかし、「私の HashMap は不変ですか」というその質問に対する述語に答える必要があります。その答えに驚かれるかもしれません。Java には、不変性を判断するための比較的複雑な一連のルールがあります。

このトピックの詳細については、Jeremy のブログ投稿をお読みください。

Java の不変性に関するパート 1: http://jeremymanson.blogspot.com/2008/04/immutability-in-java.html

Java の不変性に関するパート 2: http://jeremymanson.blogspot.com/2008/07/immutability-in-java-part-2.html

Java の不変性に関するパート 3: http://jeremymanson.blogspot.com/2008/07/immutability-in-java-part-3.html

于 2008-09-19T18:56:36.713 に答える
37

読み取りは同期の観点からは安全ですが、メモリの観点からは安全ではありません。これは、ここStackoverflowを含むJava開発者の間で広く誤解されていることです。(証拠として、この回答の評価を観察してください。)

他のスレッドを実行している場合、現在のスレッドからのメモリ書き込みがないと、HashMapの更新されたコピーが表示されない可能性があります。メモリ書き込みは、synchronizedまたはvolatileキーワードを使用するか、一部のJava同時実行構造を使用して発生します。

詳細については、新しいJavaメモリモデルに関するBrianGoetzの記事を参照してください。

于 2008-09-19T19:10:10.317 に答える
10

もう少し調べたところ、Javaドキュメントでこれを見つけました(強調は私のものです):

この実装は同期されていないことに注意してください。 複数のスレッドが同時にハッシュ マップにアクセスし、少なくとも 1 つのスレッドがマップを構造的に変更する場合は、外部で同期する必要があります。(構造変更とは、1 つ以上のマッピングを追加または削除する操作です。インスタンスに既に含まれているキーに関連付けられた値を変更するだけでは、構造変更にはなりません。)

これは、そこにあるステートメントの逆が真であると仮定すると、安全であることを暗示しているようです。

于 2008-09-19T18:20:40.137 に答える
8

1 つの注意点は、状況によっては、非同期の HashMap からの get() が無限ループを引き起こす可能性があることです。これは、同時 put() によって Map の再ハッシュが発生した場合に発生する可能性があります。

http://lightbody.net/blog/2005/07/hashmapget_can_cause_an_infini.html

于 2008-09-19T21:13:09.287 に答える
7

ただし、重要なひねりがあります。マップへのアクセスは安全ですが、一般に、すべてのスレッドが HashMap のまったく同じ状態 (したがって値) を参照することは保証されていません。これは、1 つのスレッド (たとえば、HashMap を生成したスレッド) によって行われた HashMap への変更がその CPU のキャッシュに存在し、メモリ フェンス操作が実行されるまで、他の CPU で実行されているスレッドによって認識されないマルチプロセッサ システムで発生する可能性があります。キャッシュの一貫性を確保します。Java言語仕様はこれについて明示的です。解決策は、メモリフェンス操作を発行するロック(同期(...))を取得することです。したがって、HashMap を設定した後、各スレッドが任意のロックを取得することが確実な場合は、その時点から、HashMap が再度変更されるまで、任意のスレッドから HashMap にアクセスしても問題ありません。

于 2008-09-19T19:16:10.410 に答える
5

http://www.ibm.com/developerworks/java/library/j-jtp03304/によると、#初期化の安全性により、HashMapを最終フィールドにすることができ、コンストラクターが終了した後、安全に公開されます。

...新しいメモリモデルでは、コンストラクターでのfinalフィールドの書き込みと、別のスレッドでのそのオブジェクトへの共有参照の初期ロードとの間に、起こる前の関係に似たものがあります。..。

于 2012-08-30T16:08:32.307 に答える
2

したがって、あなたが説明したシナリオは、大量のデータを Map に入れる必要があるということです。データの取り込みが完了したら、それを不変として扱います。「安全」な (実際に不変として扱われることを強制している) 1 つのアプローチは、参照をCollections.unmodifiableMap(originalMap)不変にする準備ができたときに参照を置き換えることです。

同時に使用した場合にマップがどのように失敗するかの例と、私が言及した推奨される回避策については、このバグ パレード エントリを確認してください: bug_id=6423457

于 2009-11-09T16:37:06.927 に答える
1

シングルスレッド コードであっても、ConcurrentHashMap を HashMap に置き換えるのは安全ではない可能性があることに注意してください。ConcurrentHashMap は、キーまたは値として null を禁止します。HashMap はそれらを禁止しません (質問しないでください)。

そのため、セットアップ中に既存のコードがコレクションに null を追加する可能性が低い場合 (おそらく何らかの障害が発生した場合)、説明に従ってコレクションを置き換えると、機能的な動作が変更されます。

そうは言っても、他に何もしなければ、HashMap からの同時読み取りは安全です。

[編集: 「同時読み取り」とは、同時変更も存在しないことを意味します。

他の回答は、これを確実にする方法を説明しています。1 つの方法は、マップを不変にすることですが、必須ではありません。たとえば、JSR133 メモリ モデルでは、スレッドの開始が同期アクションであると明示的に定義されています。つまり、スレッド B を開始する前にスレッド A で行われた変更は、スレッド B に表示されます。

私の意図は、Java メモリ モデルに関するより詳細な回答と矛盾しないことです。この回答は、並行性の問題は別として、ConcurrentHashMap と HashMap の間に少なくとも 1 つの API の違いがあることを指摘することを目的としています。

于 2008-09-19T18:38:06.257 に答える
0

初期化とすべての書き込みが同期されている場合は、保存されます。

クラスローダーが同期を処理するため、次のコードは保存されます。

public static final HashMap<String, String> map = new HashMap<>();
static {
  map.put("A","A");

}

volatile の書き込みによって同期が処理されるため、次のコードは保存されます。

class Foo {
  volatile HashMap<String, String> map;
  public void init() {
    final HashMap<String, String> tmp = new HashMap<>();
    tmp.put("A","A");
    // writing to volatile has to be after the modification of the map
    this.map = tmp;
  }
}

final も揮発性であるため、これはメンバー変数が final の場合にも機能します。メソッドがコンストラクターの場合。

于 2016-09-28T11:38:27.630 に答える
0

http://www.docjar.com/html/api/java/util/HashMap.java.html

ここに HashMap のソースがあります。おわかりのように、ロック/ミューテックス コードはまったくありません。

つまり、マルチスレッドの状況で HashMap から読み取ることは問題ありませんが、複数の書き込みがある場合は ConcurrentHashMap を使用することは間違いありません。

興味深いのは、.NET HashTable と Dictionary<K,V> の両方に同期コードが組み込まれていることです。

于 2008-09-19T18:19:21.857 に答える