简体   繁体   English

迭代ConcurrentHashMap值是否安全?

[英]Is iterating ConcurrentHashMap values thread safe?

In javadoc for ConcurrentHashMap is the following: ConcurrentHashMap的 javadoc中有以下内容:

Retrieval operations (including get) generally do not block, so may overlap with update operations (including put and remove). 检索操作(包括get)通常不会阻塞,因此可能与更新操作(包括put和remove)重叠。 Retrievals reflect the results of the most recently completed update operations holding upon their onset. 检索反映了最近完成的更新操作的结果。 For aggregate operations such as putAll and clear, concurrent retrievals may reflect insertion or removal of only some entries. 对于诸如putAll和clear之类的聚合操作,并发检索可能反映仅插入或删除某些条目。 Similarly, Iterators and Enumerations return elements reflecting the state of the hash table at some point at or since the creation of the iterator/enumeration. 类似地,Iterators和Enumerations在迭代器/枚举的创建时或之后的某个时刻返回反映哈希表状态的元素。 They do not throw ConcurrentModificationException. 它们不会抛出ConcurrentModificationException。 However, iterators are designed to be used by only one thread at a time. 但是,迭代器设计为一次只能由一个线程使用。

What does it mean? 这是什么意思? What happens if I try to iterate the map with two threads at the same time? 如果我尝试同时使用两个线程迭代地图会发生什么? What happens if I put or remove a value from the map while iterating it? 如果我在迭代时从地图中添加或删除值会发生什么?

What does it mean? 这是什么意思?

That means that each iterator you obtain from a ConcurrentHashMap is designed to be used by a single thread and should not be passed around. 这意味着您从ConcurrentHashMap获取的每个迭代器都被设计为由单个线程使用,不应传递。 This includes the syntactic sugar that the for-each loop provides. 这包括for-each循环提供的语法糖。

What happens if I try to iterate the map with two threads at the same time? 如果我尝试同时使用两个线程迭代地图会发生什么?

It will work as expected if each of the threads uses it's own iterator. 如果每个线程都使用它自己的迭代器,它将按预期工作。

What happens if I put or remove a value from the map while iterating it? 如果我在迭代时从地图中添加或删除值会发生什么?

It is guaranteed that things will not break if you do this (that's part of what the "concurrent" in ConcurrentHashMap means). 如果你这样做,保证不会破坏(这是ConcurrentHashMap “并发”的一部分)。 However, there is no guarantee that one thread will see the changes to the map that the other thread performs (without obtaining a new iterator from the map). 但是,无法保证一个线程会看到另一个线程执行的映射更改(无需从映射中获取新的迭代器)。 The iterator is guaranteed to reflect the state of the map at the time of it's creation. 迭代器保证在创建时反映地图的状态。 Futher changes may be reflected in the iterator, but they do not have to be. 未来的变化可能会反映在迭代器中,但它们并非必须如此。

In conclusion, a statement like 总之,一个声明就像

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

will be fine (or at least safe) almost every time you see it. 几乎每次看到它都会很好(或至少是安全的)。

You may use this class to test two accessing threads and one mutating the shared instance of ConcurrentHashMap : 您可以使用此类来测试两个访问线程,并使用一个变更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);
  }
}

No exception will be thrown. 不会抛出异常。

Sharing the same iterator between accessor threads can lead to deadlock: 在访问者线程之间共享相同的迭代器可能会导致死锁:

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);
  }
}

As soon as you start sharing the same Iterator<Map.Entry<String, String>> among accessor and mutator threads java.lang.IllegalStateException s will start popping up. 一旦开始在访问器和mutator线程中共享相同的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);
  }
}

It means that you should not share an iterator object among multiple threads. 这意味着您不应在多个线程之间共享迭代器对象。 Creating multiple iterators and using them concurrently in separate threads is fine. 创建多个迭代器并在不同的线程中同时使用它们很好。

This might give you a good insight 可能会给你一个很好的洞察力

ConcurrentHashMap achieves higher concurrency by slightly relaxing the promises it makes to callers. ConcurrentHashMap通过稍微放松它对调用者的承诺来实现更高的并发性。 A retrieval operation will return the value inserted by the most recent completed insert operation, and may also return a value added by an insertion operation that is concurrently in progress (but in no case will it return a nonsense result). 检索操作将返回最近完成的插入操作所插入的值,并且还可以返回由同时进行的插入操作添加的值(但在任何情况下都不会返回无意义的结果)。 Iterators returned by ConcurrentHashMap.iterator() will return each element once at most and will not ever throw ConcurrentModificationException, but may or may not reflect insertions or removals that occurred since the iterator was constructed . ConcurrentHashMap.iterator()返回的迭代器将最多返回一次元素,并且不会抛出ConcurrentModificationException,但可能会或可能不会反映自构造迭代器以来发生的插入或删除 No table-wide locking is needed (or even possible) to provide thread-safety when iterating the collection. 在迭代集合时,不需要表格范围的锁定(甚至可能)来提供线程安全性。 ConcurrentHashMap may be used as a replacement for synchronizedMap or Hashtable in any application that does not rely on the ability to lock the entire table to prevent updates. ConcurrentHashMap可以在任何不依赖于锁定整个表以防止更新的应用程序中用作synchronizedMap或Hashtable的替代。

Regarding this: 关于这个:

However, iterators are designed to be used by only one thread at a time. 但是,迭代器设计为一次只能由一个线程使用。

It means, while using iterators produced by ConcurrentHashMap in two threads are safe, it may cause an unexpected result in the application. 这意味着,虽然在两个线程中使用ConcurrentHashMap生成的迭代器是安全的,但它可能会在应用程序中导致意外结果。

What does it mean? 这是什么意思?

It means that you should not try to use the same iterator in two threads. 这意味着您不应该尝试在两个线程中使用相同的迭代器。 If you have two threads that need to iterate over the keys, values or entries, then they each should create and use their own iterators. 如果你有两个线程需要遍历键,值或条目,那么它们每个都应该创建并使用它们自己的迭代器。

What happens if I try to iterate the map with two threads at the same time? 如果我尝试同时使用两个线程迭代地图会发生什么?

It is not entirely clear what would happen if you broke this rule. 如果违反这条规则会发生什么情况并不完全清楚。 You could just get confusing behavior, in the same way that you do if (for example) two threads try to read from standard input without synchronizing. 您可能会遇到令人困惑的行为,就像您(例如)两个线程尝试从标准输入读取而不进行同步一样。 You could also get non-thread-safe behavior. 您还可以获得非线程安全行为。

But if the two threads used different iterators, you should be fine. 但如果两个线程使用不同的迭代器,你应该没问题。

What happens if I put or remove a value from the map while iterating it? 如果我在迭代时从地图中添加或删除值会发生什么?

That's a separate issue, but the javadoc section that you quoted adequately answers it. 这是一个单独的问题,但你引用的javadoc部分足以回答它。 Basically, the iterators are thread-safe, but it is not defined whether you will see the effects of any concurrent insertions, updates or deletions reflected in the sequence of objects returned by the iterator. 基本上,迭代器是线程安全的,但是没有定义是否会看到迭代器返回的对象序列中反映的任何并发插入,更新或删除的影响。 In practice, it probably depends on where in the map the updates occur. 实际上,它可能取决于更新发生在地图中的位置。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM