迭代ConcurrentHashMap值线程安全吗?

在javadoc中用于ConcurrentHashMap的是:

检索操作(包括get)通常不会阻塞,因此可能与更新操作(包括put和remove)重叠。 检索反映了最近完成的更新操作的结果。 对于像putAll和clear这样的集合操作,并发检索可能反映只插入或删除一些条目。 类似地,迭代器和枚举在创build迭代器/枚举时或之后返回反映哈希表状态的元素。 他们不会抛出ConcurrentModificationException。 但是,迭代器一次只能被一个线程使用。

这是什么意思? 如果我尝试用两个线程同时迭代地图会发生什么? 如果我在迭代时从地图上放置或删除一个值,会发生什么?

这是什么意思?

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

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

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

如果我在迭代时从地图上放置或删除一个值,会发生什么?

如果你这样做的话,保证事情不会中断(这是ConcurrentHashMap “并发”的一部分)。 但是,不能保证一个线程会看到另一个线程执行的地图变化(不需要从地图获取新的迭代器)。 迭代器保证在创build时反映地图的状态。 迭代器中可能会反映出更多的变化,但它们不一定是。

总之,这样的陈述

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

几乎每次看到它都会很好(或者至less是安全的)。

你可以使用这个类来testing两个访问线程,并且使用一个变异的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); } } 

将不会抛出exception。

在访问器线程之间共享相同的迭代器会导致死锁:

 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.IllegalStateExceptionexception将会popup。

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

这意味着你不应该在多个线程中共享一个迭代器对象。 创build多个迭代器并在单独的线程中同时使用它们是很好的。

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

ConcurrentHashMap通过略微放宽对调用者的承诺来实现更高的并发性。 检索操作将返回最近一次完成的插入操作插入的值,还可能返回一个正在同时进行的插入操作添加的值(但绝不会返回无意的结果)。 由ConcurrentHashMap.iterator()返回的迭代器最多返回一个元素,并且不会抛出ConcurrentModificationExceptionexception,但是可能会或可能不会反映自构build迭代器以来发生的插入或清除 。 在迭代集合时,不需要(甚至可能)提供表全locking来提供线程安全性。 ConcurrentHashMap可以在任何不依赖locking整个表的能力来防止更新的应用程序中用作synchronizedMap或Hashtable的替代品。

关于这个:

但是,迭代器一次只能被一个线程使用。

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

这是什么意思?

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

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

如果你违反了这条规则,将不会发生什么事。 如果(例如)两个线程试图从标准input中读取而没有同步,你可能会产生混淆的行为。 你也可以得到非线程安全的行为。

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

如果我在迭代时从地图上放置或删除一个值,会发生什么?

这是一个单独的问题,但您引用的javadoc部分充分回答了这个问题。 基本上,迭代器是线程安全的,但是没有定义是否会看到由迭代器返回的对象序列中反映的任何并发插入,更新或删除的效果。 实际上,这可能取决于地图中更新发生的位置。