Java 复制HashMap的线程安全方法

Java 复制HashMap的线程安全方法,java,Java,我有一个场景,希望以线程安全的方式将HashMap复制到新的HashMap中。我想这样做是为了避免复制映射时出现任何并发修改异常。您需要确保在创建新映射时未修改原始哈希映射。最直接的方法是在原始地图上同步: HashMap<K, V> original = ... HashMap<K, V> copy; synchronized (original) { copy = new HashMap<>(original); } 在我的示例代码中,只有当可

我有一个场景,希望以线程安全的方式将HashMap复制到新的HashMap中。我想这样做是为了避免复制映射时出现任何并发修改异常。

您需要确保在创建新映射时未修改原始哈希映射。最直接的方法是在原始地图上同步:

HashMap<K, V> original = ...

HashMap<K, V> copy;
synchronized (original) {
    copy = new HashMap<>(original);
}
在我的示例代码中,只有当可能修改原始对象的所有代码都在同一个原始对象上同步时,这才可以以线程安全的方式工作,尽管它可以是任何共享对象。如果original是Collections.synchronizedMap返回的对象,则必须在original上进行同步,才能使所有同步正常工作;由于同步映射的内部操作方式,使用另一个锁对象将无法工作。

首先,如果没有某种外部同步,跨多个线程使用HashMap是不安全的。如果您目前正在这样做,并且没有遇到问题,那么这只是运气,在不久的将来,您将看到非常奇怪的bug,甚至崩溃

最简单的选择是使用ConcurrentHashMap,它被设计为可由多个线程同时安全使用。然后,您可以随时将地图安全地复制到另一个地图中

正如Ted Hopp提到的,使用同步块是一种潜在的替代方法,但如果不正确执行,则更容易出错

还要注意,即使您没有跨多个线程使用HashMap,也可以获得ConcurrentModificationException。例如:

for (K key : map.keySet()) {
  map.remove(key);
}
将导致ConcurrentModificationException,因为映射在试图迭代其内容的同时被修改。因此,仅仅看到CME并不意味着您需要线程安全解决方案,您可能只需要更改修改集合的方式:

Set<K> keysSnapshot = new HashSet<>(map.keySet());
for (K key : keysSnapshot) {
  map.remove(key);
}

您必须避免在复制映射时写入映射,可能是通过同步。除非您有权访问并可以修改写入映射的代码中的每个位置,否则您就不走运了。或者,在实例化时替换ConcurrentHashMap。如果您无法控制映射的访问和类型,一个棘手的解决方法是捕获CME并重试几次,以期幸运。成功率显然取决于映射的写入频率。如果我只对映射的键集感兴趣,toArray方法是否会像预期的那样工作。我认为它不应该,但不能确定您是否真的跨多个线程使用HashMap?您是否真的遇到了ConcurrentModificationException,或者您只是担心可能会看到一个?到目前为止,您尝试了什么?map.keySet线程安全吗?因为我认为它还使用迭代器。不复制到新的HashMap会导致keySet迭代器实际运行并导致ConcurrentModificationException如果a您只在一个线程中使用映射,b您不同时修改映射,则不会得到ConcurrentModificationException您迭代它的时间。这就是为什么第二个示例首先将密钥复制到一个集合中对于HashMap的单线程使用是安全的。在不使用高级同步的情况下,如何使用ConcurrentHashMap实现线程安全的复制操作?您到底关心什么?您可以安全地访问ConcurrentHashMap的内容,即使它正在被其他线程修改。您不能确保其他任何东西都不会同时修改地图,但这通常是一项功能。如果你想阻止映射的任何突变,你确实应该在更高的级别上同步,但是如果你这样做了,那么一个标准的HashMap就可以很好地工作了。示例是只在副本上进行同步,但修改地图的其他代码可以不进行同步…@Boskopovic-您阅读了我的全部答案吗?我特别指出,每个线程上的代码都需要遵守同步。如果至少有一个线程上的代码忽略了线程安全性,我建议您提供任何线程安全解决方案。对不起,您是对的。您写道,修改表的所有部分都需要同步。