私の Java クラスでは、 Hashmap
変数 (クラス プロパティ) を含め、書き込み専用のスレッドをいくつか実行しますHashMap
。put()
書き込みが発生するたびに、一意のキーが格納されます (これは設計によって行われます)。
クラスメソッドのキーワードはsynchronized
書き込み専用で、広告セーフ条件に十分ですか? 私の HashMap はシンプルで、ConcurrentHashMap
?
私の Java クラスでは、 Hashmap
変数 (クラス プロパティ) を含め、書き込み専用のスレッドをいくつか実行しますHashMap
。put()
書き込みが発生するたびに、一意のキーが格納されます (これは設計によって行われます)。
クラスメソッドのキーワードはsynchronized
書き込み専用で、広告セーフ条件に十分ですか? 私の HashMap はシンプルで、ConcurrentHashMap
?
いいえ、書き込みを同期するだけでは十分ではありません。メモリへの読み取りと書き込みの両方に同期を適用する必要があります。
他のスレッドが、どこかで、いつか、マップを読み取る必要があり (そうでなければ、なぜマップがあるのでしょうか?)、そのスレッドは、マップによって表されるメモリを正しく表示するために同期する必要があります。また、更新中のマップ状態の一時的な不一致につまずくのを避けるために、それらを同期する必要があります。
仮定の例として、スレッド 1 がハッシュマップを書き込み、その結果が CPU 1 のレベル 1 キャッシュのみに格納されるとします。次に、スレッド 2 が数秒後に実行可能になり、CPU 2 で再開されます。CPU 2 のレベル 1 キャッシュからのハッシュマップを読み取ります。書き込みスレッドと読み取りスレッドの両方で、書き込みと読み取りの間にメモリ バリア操作がなかったため、スレッド 1 が行った書き込みは表示されません。スレッド 1 が書き込みを同期したとしても、書き込みの影響はメイン メモリにフラッシュされますが、読み取りはレベル 1 キャッシュからのものであるため、スレッド 2 は書き込みを認識しません。したがって、書き込みの同期は、書き込みの衝突を防ぐだけです。
CPU キャッシングに加えて、JMM はスレッドがデータをプライベートにキャッシュすることを可能にし、メモリ バリア (同期、いくつかの特別な制限付きの揮発性、または JMM 5+ での不変オブジェクトの構築の完了) でメイン メモリにフラッシュするだけで済みます。
スレッド化のこの複雑な問題を完全に理解するには、Java メモリ モデルを調べて学習する必要があります。これは、スレッド間でデータを共有することへの影響です。さまざまなレベルの CPU キャッシュを備えたマルチコア CPU の今日の世界でデータを共有することの複雑さを理解するには、「前に発生する」関係とメモリの可視性の概念を理解する必要があります。
JMM を理解するために時間を費やしたくない場合、簡単なルールは、2 つのスレッドが、一方のスレッドの書き込みと読み取りの間で同じオブジェクト上でどこか/何らかの方法で同期し、他方のスレッドの操作の影響を確認することです。 . 限目。これは、オブジェクトに対するすべての書き込みと読み取りを同期する必要があるという意味ではありません。1 つのスレッドでオブジェクトを作成および構成してから、それを他のスレッドに「公開」することは正当です。ただし、パブリッシング スレッドとフェッチ スレッドがハンドオーバーのために同じオブジェクトで同期している場合に限ります。
synchronized
メソッドの署名に修飾子を追加するだけで問題ありません。実際の動作を示す簡単な例を作成しました。ループを変更して、必要な数のスレッドを設定できます。
同じキー時間を追加しようとします。n
同時実行性の問題がある場合は、マップに重複したキーが含まれている必要があります。
class MyMap{
private Map<String, Object> map;
public MyMap(){
map = new HashMap<String, Object>();
}
public synchronized void put(String key, Object value){
map.put(key, value);
}
public Map<String, Object> getMap(){
return map;
}
}
class MyRunnable implements Runnable{
private MyMap clazz;
public MyRunnable(MyMap clazz){
this.clazz = clazz;
}
@Override
public void run(){
clazz.put("1", "1");
}
}
public class Test{
public static void main(String[] args) throws Exception{
MyMap c = new MyMap();
for(int i = 0 ; i < 1000 ; i ++){
new Thread(new MyRunnable(c)).start();
}
for(Map.Entry<String, Object> entry : c.getMap().entrySet()){
System.out.println(entry);
}
}
}
write メソッドは、次のsynchronized
場合に限り、スレッド セーフに十分です。
get()
ハッシュ マップが途中で変更されているときにa が呼び出されるとどうなるか想像してみてください。ハッシュマップに書き込むと同時にハッシュマップから読み取る必要がある場合、最後のポイントは最悪です。ConcurrentHashMap
この場合に使用します。
ハッシュマップへの同時書き込みがたくさんあり、それを 1 つのスレッドだけで読み取る場合、ソリューションは問題ありません。