Java同期ブロックとCollections.synchronizedMap


85

次のコードは、の呼び出しを正しく同期するように設定されていsynchronizedMapますか?

public class MyClass {
  private static Map<String, List<String>> synchronizedMap = Collections.synchronizedMap(new HashMap<String, List<String>>());

  public void doWork(String key) {
    List<String> values = null;
    while ((values = synchronizedMap.remove(key)) != null) {
      //do something with values
    }
  }

  public static void addToMap(String key, String value) {
    synchronized (synchronizedMap) {
      if (synchronizedMap.containsKey(key)) {
        synchronizedMap.get(key).add(value);
      }
      else {
        List<String> valuesList = new ArrayList<String>();
        valuesList.add(value);
        synchronizedMap.put(key, valuesList);
      }
    }
  }
}

私の理解では、addToMap()別のスレッドが呼び出されないようにするため、remove()またはcontainsKey()呼び出しを通過する前にput()同期ブロックをdoWork()入力する必要がありますが、最初にマップを作成したため、戻るaddToMap()前に別のスレッドが同期ブロックに入ることができないため、同期ブロックを入力する必要はありませんremove()Collections.synchronizedMap()。あれは正しいですか?これを行うためのより良い方法はありますか?

回答:


90

Collections.synchronizedMap() マップ上で実行する各アトミック操作が同期されることを保証します。

ただし、マップ上で2つ(またはそれ以上)の操作を実行するには、ブロック内で同期する必要があります。そうです-あなたは正しく同期しています。


26
javadocsは、syncizedMapが内部ロックではなく、マップ自体で同期することを明示的に示しているため、これが機能することを言及するのは良いことだと思います。その場合、synchronized(synchronizedMap)は正しくありません。
extraneon

2
@Yuvalあなたの答えをもう少し詳しく説明していただけますか?sychronizedMapは操作をアトミックに行うと言いますが、syncMapがすべての操作をアトミックにした場合、なぜ独自の同期ブロックが必要になるのでしょうか。あなたの最初の段落は、2番目の段落について心配することを排除しているようです。
アルメル2015

@almel私の答えを
Sergey

2
マップがすでに使用しているのに、なぜ同期ブロックが必要なの Collections.synchronizedMap()ですか?私は2番目のポイントを得ていません。
Bimal Sharma 2016

15

JDK 6を使用している場合は、ConcurrentHashMapを確認することをお勧めします。

そのクラスのputIfAbsentメソッドに注意してください。


13

コードに微妙なバグが発生する可能性があります。

[更新: 彼はmap.remove()を使用しているため、この説明は完全には有効ではありません。私は初めてその事実を見逃しました。:(それを指摘してくれた質問の作者に感謝します。残りはそのままにしておきますが、潜在的にバグがあると言うようにリードステートメントを変更しました。]

doWork()あなたは、スレッドセーフな方法で地図からリスト値を取得します。しかし、その後、あなたは危険な問題でそのリストにアクセスしています。たとえば、あるスレッドがdoWork()のリストを使用しているときに、別のスレッドがaddToMap()のsynchronizedMap.get(key).add(value)呼び出す場合があります。これらの2つのアクセスは同期されていません。経験則では、コレクションのスレッドセーフ保証は、それらが格納するキーまたは値には拡張されません。

同期リストを次のようにマップに挿入することで、これを修正できます。

List<String> valuesList = new ArrayList<String>();
valuesList.add(value);
synchronizedMap.put(key, Collections.synchronizedList(valuesList)); // sync'd list

または、doWork()でリストにアクセスしているときに、マップ上で同期することもできます。

  public void doWork(String key) {
    List<String> values = null;
    while ((values = synchronizedMap.remove(key)) != null) {
      synchronized (synchronizedMap) {
          //do something with values
      }
    }
  }

最後のオプションは同時実行性を少し制限しますが、IMOはやや明確です。

また、ConcurrentHashMapについての簡単なメモ。これは本当に便利なクラスですが、同期されたHashMapの適切な代替とは限りません。そのJavadocから引用して、

このクラスは、スレッドセーフに依存しているが、同期の詳細には依存していないプログラムで、Hashtableと完全に相互運用可能です。

言い換えると、putIfAbsent()はアトミック挿入には最適ですが、その呼び出し中にマップの他の部分が変更されないことを保証するものではありません。原子性のみを保証します。サンプルプログラムでは、put()以外のことについて、(同期された)HashMapの同期の詳細に依存しています。

最後のもの。:) Java Concurrency in Practiceからのこの素晴らしい引用は、デバッグ用のマルチスレッドプログラムの設計に常に役立ちます。

複数のスレッドがアクセスする可能性のある可変状態変数ごとに、その変数へのすべてのアクセスは、同じロックを保持した状態で実行する必要があります。


私がsynchronizedMap.get()でリストにアクセスしていた場合、バグについてのあなたの指摘がわかります。remove()を使用しているので、そのキーを使用して次に追加すると、新しいArrayListが作成され、doWorkで使用しているものと干渉しないようにする必要がありますか?
ライアンアハーン

正しい!私はあなたの除去を過ぎて完全にそよ風を吹きました。
JLR

1
複数のスレッドがアクセスする可能性のある可変状態変数ごとに、その変数へのすべてのアクセスは、同じロックを保持した状態で実行する必要があります。----私は通常、新しいObject()だけのプライベートプロパティを追加し、それを同期ブロックに使用します。そうすれば、私はその文脈の生を通してそのすべてを知ることができます。同期(objectInVar){}
AnthonyJClink 2014年

11

はい、正しく同期しています。これについて詳しく説明します。同期マップオブジェクトの一連のメソッド呼び出しで、後続のメソッド呼び出しの前のメソッド呼び出しの結果に依存する必要がある場合にのみ、synchronizedMapオブジェクトの2つ以上のメソッド呼び出しを同期する必要があります。このコードを見てみましょう:

synchronized (synchronizedMap) {
    if (synchronizedMap.containsKey(key)) {
        synchronizedMap.get(key).add(value);
    }
    else {
        List<String> valuesList = new ArrayList<String>();
        valuesList.add(value);
        synchronizedMap.put(key, valuesList);
    }
}

このコードでは

synchronizedMap.get(key).add(value);

そして

synchronizedMap.put(key, valuesList);

メソッド呼び出しは、前の結果に依存しています

synchronizedMap.containsKey(key)

メソッド呼び出し。

メソッド呼び出しのシーケンスが同期されていない場合、結果が間違っている可能性があります。たとえばthread 1、メソッドaddToMap()を実行していて、メソッドをthread 2実行doWork() しているsynchronizedMapオブジェクトに対するメソッド呼び出しのシーケンスは次のようになります。 Thread 1メソッドを実行しました

synchronizedMap.containsKey(key)

結果は「true」です。その後、オペレーティングシステムは実行制御をに切り替えthread 2て実行しました

synchronizedMap.remove(key)

その後、実行制御がに戻さthread 1れ、たとえば実行されました。

synchronizedMap.get(key).add(value);

synchronizedMapオブジェクトにが含まれているkeyと信じると、 が返さNullPointerExceptionれるためにスローされsynchronizedMap.get(key)ますnullsynchronizedMapオブジェクトに対するメソッド呼び出しのシーケンスが相互の結果に依存していない場合は、シーケンスを同期する必要はありません。たとえば、次のシーケンスを同期する必要はありません。

synchronizedMap.put(key1, valuesList1);
synchronizedMap.put(key2, valuesList2);

ここに

synchronizedMap.put(key2, valuesList2);

メソッド呼び出しは、前の結果に依存しません

synchronizedMap.put(key1, valuesList1);

メソッド呼び出し(2つのメソッド呼び出しの間に何らかのスレッドが干渉し、たとえばを削除したかどうかは関係ありませんkey1)。


4

それは私には正しいように見えます。何かを変更する場合は、Collections.synchronizedMap()の使用を停止し、すべてを同じ方法で同期して、わかりやすくします。

また、私は交換します

  if (synchronizedMap.containsKey(key)) {
    synchronizedMap.get(key).add(value);
  }
  else {
    List<String> valuesList = new ArrayList<String>();
    valuesList.add(value);
    synchronizedMap.put(key, valuesList);
  }

List<String> valuesList = synchronziedMap.get(key);
if (valuesList == null)
{
  valuesList = new ArrayList<String>();
  synchronziedMap.put(key, valuesList);
}
valuesList.add(value);

3
やること。Collections.synchronizedXXX()毎日のアプリのロジックでオブジェクト(ほとんどの場合、コレクション自体になります)で同期する必要があるのに、なぜAPIを使用する必要があるのか​​わかりません
kellogs 2015

3

同期した方法は正しいです。しかし、落とし穴があります

  1. コレクションフレームワークによって提供される同期ラッパーは、メソッド呼び出し、つまりadd / get / containsが相互に排他的に実行されることを保証します。

ただし、実際の世界では、通常、値を入力する前にマップにクエリを実行します。したがって、2つの操作を実行する必要があり、したがって同期ブロックが必要になります。だからあなたがそれを使った方法は正しいです。しかしながら。

  1. コレクションフレームワークで利用可能なMapの並行実装を使用することもできます。「ConcurrentHashMap」の利点は

a。同じことをより効率的に行うAPI「putIfAbsent」があります。

b。その効率的:dCocurrentMapはキーをロックするだけなので、マップの世界全体をブロックすることはありません。キーと値をブロックしたところ。

c。マップオブジェクトの参照をコードベースの別の場所に渡した可能性があります。そこでは、ティーンの他の開発者が誤って使用してしまう可能性があります。つまり、マップのオブジェクトをロックせずに、すべてadd()またはget()を実行できます。したがって、彼の呼び出しは、同期ブロックに対して相互に排他的に実行されません。ただし、並行実装を使用すると、誤って使用/実装することは決してないという安心感が得られます。


2

Googleコレクションをチェックしてください' Multimap、例えばこのプレゼンテーションの28ページ。

何らかの理由でそのライブラリを使用できない場合ConcurrentHashMapSynchronizedHashMap、;の代わりに使用することを検討してください。putIfAbsent(K,V)まだ存在しない場合は、要素リストをアトミックに追加できる気の利いたメソッドがあります。また、CopyOnWriteArrayList使用パターンで必要な場合は、マップ値に使用することを検討してください。

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