首页 文章

迭代ConcurrentHashMap值是否安全?

提问于
浏览
132

ConcurrentHashMap的javadoc中如下:

检索操作(包括get)一般不会阻塞,因此可能与更新操作重叠(包括put和remove) . 检索反映了最近完成的更新操作的结果 . 对于诸如putAll和clear之类的聚合操作,并发检索可能反映仅插入或删除某些条目 . 类似地,Iterators和Enumerations在迭代器/枚举的创建时或之后的某个时刻返回反映哈希表状态的元素 . 它们不会抛出ConcurrentModificationException . 但是,迭代器设计为一次只能由一个线程使用 .

这是什么意思?如果我尝试同时使用两个线程迭代 Map 会发生什么?如果我在迭代时放置或删除 Map 中的值,会发生什么?

5 回答

  • 9

    您可以使用此类测试两个访问线程,一个变更 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);
      }
    }
    

    一旦你开始在访问器和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);
      }
    }
    
  • 4

    这意味着您不应在多个线程之间共享迭代器对象 . 创建多个迭代器并在不同的线程中同时使用它们很好 .

  • 7

    This可能会给你一个很好的见解

    ConcurrentHashMap通过稍微放松它对调用者的承诺来实现更高的并发性 . 检索操作将返回由最近完成的插入操作插入的值,并且还可以返回由同时进行的插入操作添加的值(但在任何情况下都不会返回无意义的结果) . ConcurrentHashMap.iterator()返回的迭代器将最多返回一次元素,并且不会抛出ConcurrentModificationException,但可能会或可能不会反映自构造迭代器以来发生的插入或删除 . 在迭代集合时,不需要表格范围的锁定(甚至可能)来提供线程安全性 . ConcurrentHashMap可以在任何不依赖于锁定整个表以防止更新的应用程序中用作synchronizedMap或Hashtable的替代 .

    关于这个:

    但是,迭代器设计为一次只能由一个线程使用 .

    这意味着,虽然在两个线程中使用ConcurrentHashMap生成的迭代器是安全的,但它可能会在应用程序中导致意外结果 .

  • 17

    这是什么意思?

    这意味着您从 ConcurrentHashMap 获得的每个迭代器都被设计为由单个线程使用,不应传递 . 这包括for-each循环提供的语法糖 .

    如果我尝试同时使用两个线程迭代 Map 会发生什么?

    如果每个线程都使用它自己的迭代器,它将按预期工作 .

    如果我在迭代时放置或删除 Map 中的值会怎样?

    如果你这样做,保证事情不会中断(这是 ConcurrentHashMap 中"concurrent"的意思) . 但是,无法保证一个线程将看到另一个线程执行的映射更改(无需从映射中获取新的迭代器) . 迭代器保证在创建时反映 Map 的状态 . 进一步的变化可能会反映在迭代器中,但它们并非必须如此 .

    总之,一个声明就像

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

    几乎每次看到它都会很好(或至少是安全的) .

  • 167

    这是什么意思?

    这意味着您不应该尝试在两个线程中使用相同的迭代器 . 如果你有两个线程需要迭代键,值或条目,那么它们每个都应该创建并使用它们自己的迭代器 .

    如果我尝试同时使用两个线程迭代 Map 会发生什么?

    如果违反这条规则会发生什么情况并不完全清楚 . 您可能会遇到令人困惑的行为,就像您(例如)两个线程尝试从标准输入读取而不进行同步一样 . 您还可以获得非线程安全行为 .

    但如果两个线程使用不同的迭代器,你应该没问题 .

    如果我在迭代时放置或删除 Map 中的值会怎样?

    这是一个单独的问题,但你引用的javadoc部分足以回答它 . 基本上,迭代器是线程安全的,但是 it is not defined 是否会看到迭代器返回的对象序列中反映的任何并发插入,更新或删除的影响 . 实际上,它可能取决于更新发生在 Map 中的位置 .

相关问题