167

ConcurrentHashMapのjavadocは次のとおりです。

通常、取得操作 (get を含む) はブロックされないため、更新操作 (put および remove を含む) と重複する場合があります。検索は、開始時に保持されている最新の更新操作の結果を反映します。putAll や clear などの集計操作の場合、同時取得では、一部のエントリのみの挿入または削除が反映される場合があります。同様に、反復子と列挙型は、反復子/列挙型の作成時または作成後のある時点でのハッシュ テーブルの状態を反映する要素を返します。それらは ConcurrentModificationException をスローしません。ただし、反復子は、一度に 1 つのスレッドだけが使用できるように設計されています。

どういう意味ですか?同時に 2 つのスレッドでマップを反復しようとするとどうなりますか? 反復中にマップに値を追加または削除するとどうなりますか?

4

5 に答える 5

207

どういう意味ですか?

つまり、から取得する各イテレータはConcurrentHashMap、単一のスレッドで使用されるように設計されており、渡されるべきではありません。これには、for-eachループが提供する構文糖衣が含まれます。

2つのスレッドで同時にマップを反復しようとするとどうなりますか?

各スレッドが独自のイテレータを使用している場合は、期待どおりに機能します。

反復中にマップに値を追加または削除するとどうなりますか?

これを行うと、物事が壊れないことが保証されます(これは「並行」のConcurrentHashMap意味の一部です)。ただし、一方のスレッドがもう一方のスレッドが実行するマップへの変更を(マップから新しいイテレーターを取得せずに)表示するという保証はありません。イテレータは、マップの作成時のマップの状態を反映することが保証されています。さらなる変更はイテレータに反映される場合がありますが、そうである必要はありません。

結論として、次のようなステートメント

for (Object o : someConcurrentHashMap.entrySet()) {
    // ...
}

あなたがそれを見るたびに大丈夫(または少なくとも安全)になります。

于 2010-09-22T11:17:50.580 に答える
18

このクラスを使用して、2 つのアクセス スレッドと の共有インスタンスを変更する 1 つのスレッドをテストできますConcurrentHashMap

import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ConcurrentMapIteration
{
  private final Map<String, String> map = new ConcurrentHashMap<String, String>();

  private final static int MAP_SIZE = 100000;

  public static void main(String[] args)
  {
    new ConcurrentMapIteration().run();
  }

  public ConcurrentMapIteration()
  {
    for (int i = 0; i < MAP_SIZE; i++)
    {
      map.put("key" + i, UUID.randomUUID().toString());
    }
  }

  private final ExecutorService executor = Executors.newCachedThreadPool();

  private final class Accessor implements Runnable
  {
    private final Map<String, String> map;

    public Accessor(Map<String, String> map)
    {
      this.map = map;
    }

    @Override
    public void run()
    {
      for (Map.Entry<String, String> entry : this.map.entrySet())
      {
        System.out.println(
            Thread.currentThread().getName() + " - [" + entry.getKey() + ", " + entry.getValue() + ']'
        );
      }
    }
  }

  private final class Mutator implements Runnable
  {

    private final Map<String, String> map;
    private final Random random = new Random();

    public Mutator(Map<String, String> map)
    {
      this.map = map;
    }

    @Override
    public void run()
    {
      for (int i = 0; i < 100; i++)
      {
        this.map.remove("key" + random.nextInt(MAP_SIZE));
        this.map.put("key" + random.nextInt(MAP_SIZE), UUID.randomUUID().toString());
        System.out.println(Thread.currentThread().getName() + ": " + i);
      }
    }
  }

  private void run()
  {
    Accessor a1 = new Accessor(this.map);
    Accessor a2 = new Accessor(this.map);
    Mutator m = new Mutator(this.map);

    executor.execute(a1);
    executor.execute(m);
    executor.execute(a2);
  }
}

例外はスローされません。

アクセサー スレッド間で同じ反復子を共有すると、デッドロックが発生する可能性があります。

import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ConcurrentMapIteration
{
  private final Map<String, String> map = new ConcurrentHashMap<String, String>();
  private final Iterator<Map.Entry<String, String>> iterator;

  private final static int MAP_SIZE = 100000;

  public static void main(String[] args)
  {
    new ConcurrentMapIteration().run();
  }

  public ConcurrentMapIteration()
  {
    for (int i = 0; i < MAP_SIZE; i++)
    {
      map.put("key" + i, UUID.randomUUID().toString());
    }
    this.iterator = this.map.entrySet().iterator();
  }

  private final ExecutorService executor = Executors.newCachedThreadPool();

  private final class Accessor implements Runnable
  {
    private final Iterator<Map.Entry<String, String>> iterator;

    public Accessor(Iterator<Map.Entry<String, String>> iterator)
    {
      this.iterator = iterator;
    }

    @Override
    public void run()
    {
      while(iterator.hasNext()) {
        Map.Entry<String, String> entry = iterator.next();
        try
        {
          String st = Thread.currentThread().getName() + " - [" + entry.getKey() + ", " + entry.getValue() + ']';
        } catch (Exception e)
        {
          e.printStackTrace();
        }

      }
    }
  }

  private final class Mutator implements Runnable
  {

    private final Map<String, String> map;
    private final Random random = new Random();

    public Mutator(Map<String, String> map)
    {
      this.map = map;
    }

    @Override
    public void run()
    {
      for (int i = 0; i < 100; i++)
      {
        this.map.remove("key" + random.nextInt(MAP_SIZE));
        this.map.put("key" + random.nextInt(MAP_SIZE), UUID.randomUUID().toString());
      }
    }
  }

  private void run()
  {
    Accessor a1 = new Accessor(this.iterator);
    Accessor a2 = new Accessor(this.iterator);
    Mutator m = new Mutator(this.map);

    executor.execute(a1);
    executor.execute(m);
    executor.execute(a2);
  }
}

Iterator<Map.Entry<String, String>>アクセサー スレッドとミューテーター スレッド間で同じものを共有し始めるとすぐに、java.lang.IllegalStateExceptionポップアップが開始されます。

import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ConcurrentMapIteration
{
  private final Map<String, String> map = new ConcurrentHashMap<String, String>();
  private final Iterator<Map.Entry<String, String>> iterator;

  private final static int MAP_SIZE = 100000;

  public static void main(String[] args)
  {
    new ConcurrentMapIteration().run();
  }

  public ConcurrentMapIteration()
  {
    for (int i = 0; i < MAP_SIZE; i++)
    {
      map.put("key" + i, UUID.randomUUID().toString());
    }
    this.iterator = this.map.entrySet().iterator();
  }

  private final ExecutorService executor = Executors.newCachedThreadPool();

  private final class Accessor implements Runnable
  {
    private final Iterator<Map.Entry<String, String>> iterator;

    public Accessor(Iterator<Map.Entry<String, String>> iterator)
    {
      this.iterator = iterator;
    }

    @Override
    public void run()
    {
      while (iterator.hasNext())
      {
        Map.Entry<String, String> entry = iterator.next();
        try
        {
          String st =
              Thread.currentThread().getName() + " - [" + entry.getKey() + ", " + entry.getValue() + ']';
        } catch (Exception e)
        {
          e.printStackTrace();
        }

      }
    }
  }

  private final class Mutator implements Runnable
  {

    private final Random random = new Random();

    private final Iterator<Map.Entry<String, String>> iterator;

    private final Map<String, String> map;

    public Mutator(Map<String, String> map, Iterator<Map.Entry<String, String>> iterator)
    {
      this.map = map;
      this.iterator = iterator;
    }

    @Override
    public void run()
    {
      while (iterator.hasNext())
      {
        try
        {
          iterator.remove();
          this.map.put("key" + random.nextInt(MAP_SIZE), UUID.randomUUID().toString());
        } catch (Exception ex)
        {
          ex.printStackTrace();
        }
      }

    }
  }

  private void run()
  {
    Accessor a1 = new Accessor(this.iterator);
    Accessor a2 = new Accessor(this.iterator);
    Mutator m = new Mutator(map, this.iterator);

    executor.execute(a1);
    executor.execute(m);
    executor.execute(a2);
  }
}
于 2010-09-22T11:18:04.713 に答える
12

これは、複数のスレッド間でイテレータオブジェクトを共有してはならないことを意味します。複数のイテレータを作成し、それらを別々のスレッドで同時に使用することは問題ありません。

于 2010-09-22T11:14:22.433 に答える
9

これはあなたに良い洞察を与えるかもしれません

ConcurrentHashMap は、呼び出し元に対する約束を少し緩和することで、より高い同時実行性を実現します。検索操作は、最後に完了した挿入操作によって挿入された値を返します。また、同時に進行中の挿入操作によって追加された値を返す場合もあります (ただし、意味のない結果が返されることはありません)。ConcurrentHashMap.iterator() によって返される反復子は、各要素を最大 1 回返し、ConcurrentModificationException をスローすることはありませんが、反復子が構築された後に発生した挿入または削除を反映する場合と反映しない場合があります。. コレクションを反復するときにスレッドセーフを提供するために、テーブル全体のロックは必要ありません (または可能です)。ConcurrentHashMap は、更新を防ぐためにテーブル全体をロックする機能に依存しないアプリケーションで、synchronizedMap または Hashtable の代わりとして使用できます。

これに関して:

ただし、反復子は、一度に 1 つのスレッドだけが使用できるように設計されています。

これは、2 つのスレッドで ConcurrentHashMap によって生成された反復子を使用することは安全ですが、アプリケーションで予期しない結果が生じる可能性があることを意味します。

于 2010-09-22T11:11:30.973 に答える
5

どういう意味ですか?

これは、2 つのスレッドで同じ反復子を使用しようとしてはならないことを意味します。キー、値、またはエントリを反復する必要がある 2 つのスレッドがある場合、それぞれが独自の反復子を作成して使用する必要があります。

同時に 2 つのスレッドでマップを反復しようとするとどうなりますか?

このルールを破るとどうなるかは完全には明らかではありません。たとえば、2 つのスレッドが同期せずに標準入力から読み取ろうとした場合と同じように、混乱を招く動作が発生する可能性があります。非スレッドセーフな動作が発生する可能性もあります。

しかし、2 つのスレッドが異なる反復子を使用していれば問題ありません。

反復中にマップに値を追加または削除するとどうなりますか?

2 つのスレッドが同じ反復子を使用している場合: 上記を参照してください。混乱を招く可能性があり、スレッドセーフではない動作をする可能性があります。

スレッドが異なる反復子を使用している場合は、引用した javadoc セクションが適切に回答します。基本的に、1 つのスレッド / イテレーターが、他のスレッド / イテレーターによって行われた同時挿入、更新、または削除の影響を受けるかどうかは定義されていません。ただし、挿入/更新/削除は、マップの同時実行プロパティに従って行われます。

于 2010-09-22T12:05:21.287 に答える