ConcurrentHashMap 和 Collections.synchronizedMap(Map) 有啥区别?

Posted

技术标签:

【中文标题】ConcurrentHashMap 和 Collections.synchronizedMap(Map) 有啥区别?【英文标题】:What's the difference between ConcurrentHashMap and Collections.synchronizedMap(Map)?ConcurrentHashMap 和 Collections.synchronizedMap(Map) 有什么区别? 【发布时间】:2010-10-05 08:50:31 【问题描述】:

我有一个要由多个线程同时修改的 Map。

Java API 中似乎存在三种不同的同步 Map 实现:

Hashtable Collections.synchronizedMap(Map) ConcurrentHashMap

据我了解,Hashtable 是一个旧实现(扩展了过时的Dictionary 类),后来经过调整以适应Map 接口。虽然它同步的,但它似乎有严重的scalability issues 并且不鼓励用于新项目。

但是另外两个呢? Collections.synchronizedMap(Map)ConcurrentHashMaps 返回的 Map 有什么区别?哪一种适合哪一种情况?

【问题讨论】:

@SmilesinaJar 链接目前已损坏,这里是本文的存档副本:Why ConcurrentHashMap is better than Hashtable and just as good as a HashMap IBM:ConcurrentHashMap 如何在不影响线程安全的情况下提供更高的并发性@ibm.com/developerworks/java/library/j-jtp08223/… 仅供参考,Java 6 带来了 ConcurrentSkipListMap 作为另一个线程安全的 Map 实现。使用Skip List 算法设计为在负载下高度并发。 【参考方案1】:

根据您的需要,请使用ConcurrentHashMap。它允许从多个线程同时修改 Map 而无需阻塞它们。 Collections.synchronizedMap(map) 创建一个阻塞的 Map 会降低性能,但可以确保一致性(如果使用得当)。

如果您需要确保数据一致性,并且每个线程都需要拥有最新的地图视图,请使用第二个选项。如果性能很关键,则使用第一个,并且每个线程只将数据插入到映射中,读取发生的频率较低。

【讨论】:

看源码,同步映射只是一个互斥量(阻塞)的实现,而ConcurrentHashMap处理并发访问比较复杂 另请注意,ConcurrentHashMap 不允许空键或空值。所以它们不是同步地图的平等替代品。 我认为你应该阅读这个http://ria101.wordpress.com/2011/12/12/concurrenthashmap-avoid-a-common-misuse/ @AbdullahShaikh 该文章中提出的问题已在 Java 7 中得到修复,并在 Java 8 中进行了进一步改进。 @hengxin:一旦你在执行一个包含多个查询或更新地图的操作,或者当你在地图上进行迭代时,你必须在地图上手动同步以确保一致性。同步映射仅保证映射上的单个操作(方法调用)的一致性,这使得它通常毫无价值,因为大多数现实生活中的操作都不是微不足道的,因此无论如何您都必须手动同步。【参考方案2】:

当您可以使用 ConcurrentHashMap 时,它是首选 - 尽管它至少需要 Java 5。

它被设计为在多线程使用时可以很好地扩展。当一次只有一个线程访问 Map 时,性能可能会稍差一些,但当多个线程同时访问 Map 时性能会好很多。

我找到了一个blog entry,它复制了优秀书籍Java Concurrency In Practice 中的一个表格,我强烈推荐。

Collections.synchronizedMap 只有在您需要用一些其他特征包装地图时才有意义,也许是某种有序地图,如 TreeMap。

【讨论】:

是的 - 似乎我在所有其他答案中都提到了那本书! @BillMichell 链接已损坏 @Govinda 在访问链接之前关闭 javascript。博客条目还在!【参考方案3】:

HashtableConcurrentHashMap 不允许使用 null 键或 null 值。

Collections.synchronizedMap(Map) 同步所有操作(getputsize 等)。

ConcurrentHashMap 支持完全并发的检索,以及可调整的预期更新并发。

像往常一样,涉及到并发——开销——速度的权衡。您确实需要考虑应用程序的详细并发要求才能做出决定,然后测试您的代码以查看它是否足够好。

【讨论】:

【参考方案4】:

ConcurrentHashMap 针对并发访问进行了优化。

访问不会锁定整个地图,而是使用更细粒度的策略,从而提高可扩展性。还有专门针对并发访问的功能增强,例如并发迭代器。

【讨论】:

【参考方案5】:

HashTable 是对的,你可以忘记它。

Your article 提到了这样一个事实,虽然 HashTable 和同步包装类通过一次只允许一个线程访问映射来提供基本的线程安全,但这并不是“真正的”线程安全,因为许多复合操作仍然需要额外的同步,例如:

synchronized (records) 
  Record rec = records.get(id);
  if (rec == null) 
      rec = new Record(id);
      records.put(id, rec);
  
  return rec;

但是,不要认为ConcurrentHashMap 是带有典型synchronized 块的HashMap 的简单替代方案,如上所示。阅读this 文章以更好地了解其复杂性。

【讨论】:

【参考方案6】:

Hashtable 的“可扩展性问题”在Collections.synchronizedMap(Map) 中以完全相同的方式存在 - 它们使用非常简单的同步,这意味着只有一个线程可以同时访问映射。

当您进行简单的插入和查找时,这不是什么大问题(除非您非常密集地执行此操作),但是当您需要遍历整个 Map 时,这会成为一个大问题,这可能需要很长时间Map - 当一个线程执行此操作时,所有其他线程如果想要插入或查找任何内容,则必须等待。

ConcurrentHashMap 使用非常复杂的技术来减少同步需求,并允许多个线程在不同步的情况下进行并行读取访问,更重要的是,它提供了一个不需要同步的Iterator,甚至允许在运行期间修改 Map interation(尽管它不保证在迭代期间插入的元素是否会被返回)。

【讨论】:

这就是我想要的! :) 不同步的迭代器只是纯粹的甜蜜!谢谢你的信息! :) (: 很好的答案..但这是否意味着在检索线程期间将无法获得最新更新,因为阅读器线程不同步。 @MrA:你问的是 ConcurrentHashMap 吗? “检索”是什么意思? @Michael Borgwardt 用于 ConcurrentHashmap,例如。假设有多个线程。其中一些正在更新地图,其中一些正在从同一张地图中获取数据。因此,在这种情况下,当线程尝试读取时,是否可以保证它们将获得已更新的最新数据,因为读取器线程不必持有锁。【参考方案7】:

一般来说,如果您想使用ConcurrentHashMap,请确保您准备好错过“更新”(即打印 HashMap 的内容并不能确保它会打印最新的 Map)和使用CyclicBarrier 之类的 API 来确保整个程序生命周期的一致性。

【讨论】:

【参考方案8】:

这里有几个:

1) ConcurrentHashMap 只锁定 Map 的一部分,但 SynchronizedMap 锁定整个 MAp。 2) ConcurrentHashMap 比 SynchronizedMap 性能更好,扩展性更强。 3) 如果是多读卡器和单写卡器,ConcurrentHashMap 是最好的选择。

本文来自Difference between ConcurrentHashMap and hashtable in Java

【讨论】:

【参考方案9】:

ConcurrentHashMap 中,锁定应用于一个段而不是整个映射。 每个段管理自己的内部哈希表。该锁仅适用于更新操作。 Collections.synchronizedMap(Map) 同步整个地图。

【讨论】:

【参考方案10】:
    如果数据一致性非常重要 - 使用 Hashtable 或 Collections.synchronizedMap(Map)。 如果速度/性能非常重要并且数据更新可能会受到影响 - 使用 ConcurrentHashMap。

【讨论】:

【参考方案11】:
╔═══════════════╦═══════════════════╦═══════════════════╦═════════════════════╗
║   Property    ║     HashMap       ║    Hashtable      ║  ConcurrentHashMap  ║
╠═══════════════╬═══════════════════╬═══════════════════╩═════════════════════╣ 
║      Null     ║     allowed       ║              not allowed                ║
║  values/keys  ║                   ║                                         ║
╠═══════════════╬═══════════════════╬═════════════════════════════════════════╣
║ Thread-safety ║                   ║                                         ║
║   features    ║       no          ║                  yes                    ║
╠═══════════════╬═══════════════════╬═══════════════════╦═════════════════════╣
║     Lock      ║       not         ║ locks the whole   ║ locks the portion   ║        
║  mechanism    ║    applicable     ║       map         ║                     ║ 
╠═══════════════╬═══════════════════╩═══════════════════╬═════════════════════╣
║   Iterator    ║               fail-fast               ║ weakly consistent   ║ 
╚═══════════════╩═══════════════════════════════════════╩═════════════════════╝

关于锁定机制: Hashtablelocks the object,而ConcurrentHashMap 锁定only the bucket。

【讨论】:

Hashtable 不是地图的锁定部分。看看实现。它使用synchronized 没有提供锁的键,所以它基本上意味着它在每个操作中锁定整个hashtable 同步地图呢? Collections.syncronizedMap 的行为类似于支持映射,除了所有方法都是线程安全的 我会打印表格并以每张 5 美元的价格出售;)。好一个@shevchyk 已编辑:两者都不是完全线程安全的。这对新开发人员来说有点误导。请参阅:ibm.com/developerworks/java/library/j-jtp07233/index.html 以了解即使 ConcurrentHashMap 对于外部数据竞争也不是完全线程安全的。 (例如:1 个线程删除一个值,然后另一个线程尝试检查它是否存在,如果不存在则放置它。这是一个数据竞争条件,仍然意味着尽管使用了“ConcurrentHashMap”,但您并没有缓解所有线程安全问题。 【参考方案12】:

这两者之间的主要区别在于ConcurrentHashMap 将仅锁定正在更新的部分数据,而其他部分数据可以被其他线程访问。但是Collections.synchronizedMap()会在更新的时候锁住所有的数据,其他线程只有在锁被释放后才能访问数据。如果更新操作比较多,读操作比较少,应该选择ConcurrentHashMap

另外一个区别是ConcurrentHashMap不会保留传入的Map中元素的顺序。在存储数据时类似于HashMap。不能保证保留元素顺序。而Collections.synchronizedMap() 将保留传入的Map 的元素顺序。例如,如果您将TreeMap 传递给ConcurrentHashMap,则ConcurrentHashMap 中的元素顺序可能与@987654331 中的顺序不同@,但Collections.synchronizedMap() 将保留顺序。

此外,ConcurrentHashMap 可以保证在一个线程正在更新映射并且另一个线程正在遍历从映射中获取的迭代器时不会抛出 ConcurrentModificationException。但是,Collections.synchronizedMap() 不能保证这一点。

one post 说明了这两者的区别,还有ConcurrentSkipListMap

【讨论】:

【参考方案13】:

ConcurrentHashMap

ConcurrentHashMap 适用于写入操作远多于读取操作的性能关键型应用程序。 它是线程安全的,无需同步整个地图。 使用锁完成写入时,读取速度可能非常快。 在对象级别没有锁定。 锁定在哈希图存储桶级别的粒度要细得多。 如果一个线程尝试修改它而另一个线程正在对其进行迭代,则 ConcurrentHashMap 不会引发 ConcurrentModificationException。 ConcurrentHashMap 使用多个锁。 读取操作是非阻塞的,而写入操作会锁定特定的段或存储桶。

SynchronizedHashMap

对象级别的同步。 每个读/写操作都需要获取锁。 锁定整个集合是一种性能开销。 这实际上只允许一个线程访问整个映射并阻止所有其他线程。 可能会引起争用。 SynchronizedHashMap 返回迭代器,它在并发修改时快速失败。

Collection.synchronizedMap()

Collections 实用程序类提供了对集合进行操作并返回包装集合的多态算法。它的 synchronizedMap() 方法提供了线程安全的功能。 当数据一致性至关重要时,我们需要使用 Collections.synchronizedMap()。

source

【讨论】:

【参考方案14】:

Collections.synchronizedMap() 方法同步 HashMap 的所有方法,并有效地将其简化为一个线程一次可以进入的数据结构,因为它将每个方法都锁定在一个公共锁上。

在 ConcurrentHashMap 中,同步的方式稍有不同。 ConcurrentHashMap 不是将每个方法都锁定在一个公共锁上,而是对单独的桶使用单独的锁,因此只锁定 Map 的一部分。 默认情况下,有 16 个存储桶,并且每个存储桶也有单独的锁。所以默认的并发级别是 16。这意味着理论上任何给定时间 16 个线程都可以访问 ConcurrentHashMap,如果它们都将要分开存储桶。

【讨论】:

【参考方案15】:

关于ConcurrentHashMap,除了它提供的并发功能之外,还有一个一个关键功能需要注意,即故障安全迭代器。我看到开发人员使用ConcurrentHashMap 只是因为他们想编辑入口集——在迭代时放置/删除。 Collections.synchronizedMap(Map) 不提供 fail-safe 迭代器,而是提供 fail-fast 迭代器。快速失败迭代器使用地图大小的快照,在迭代期间无法编辑。

【讨论】:

【参考方案16】:

我们可以通过使用 ConcurrentHashMap 和 synchronisedHashmap 和 Hashtable 来实现线程安全。但是如果你看看他们的架构,就会有很大的不同。

    synchronisedHashmap 和 Hashtable

两者都将保持对象级别的锁定。因此,如果您想执行 put/get 之类的任何操作,则必须先获取锁。同时,不允许其他线程执行任何操作。因此,一次只能有一个线程对此进行操作。所以这里的等待时间会增加。与 ConcurrentHashMap 相比,我们可以说性能相对较低。

    ConcurrentHashMap

它将保持段级别的锁定。它有 16 个段,默认保持并发级别为 16。所以一次可以有 16 个线程对 ConcurrentHashMap 进行操作。此外,读操作不需要锁。所以任意数量的线程都可以对其执行get操作。

如果thread1想在segment 2上执行put操作,thread2想在segment 4上执行put操作,那么这里是允许的。也就是说,16个线程一次可以对ConcurrentHashMap进行update(put/delete)操作。

这样在这里的等待时间会更少。因此性能相对优于 synchronisedHashmap 和 Hashtable。

【讨论】:

,1。如果多个线程试图编辑同一个块会发生什么? 2. 如果说两个线程尝试从同一个块中读取数据而另一个线程同时写入数据会发生什么?【参考方案17】:

除了建议的内容,我想发布与SynchronizedMap相关的源代码。

为了使Map线程安全,我们可以使用Collections.synchronizedMap语句并输入地图实例作为参数。

CollectionssynchronizedMap的实现如下所示

   public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) 
        return new SynchronizedMap<>(m);
    

如您所见,输入 Map 对象被 SynchronizedMap 对象包裹。 让我们深入研究SynchronizedMap的实现,

 private static class SynchronizedMap<K,V>
        implements Map<K,V>, Serializable 
        private static final long serialVersionUID = 1978198479659022715L;

        private final Map<K,V> m;     // Backing Map
        final Object      mutex;        // Object on which to synchronize

        SynchronizedMap(Map<K,V> m) 
            this.m = Objects.requireNonNull(m);
            mutex = this;
        

        SynchronizedMap(Map<K,V> m, Object mutex) 
            this.m = m;
            this.mutex = mutex;
        

        public int size() 
            synchronized (mutex) return m.size();
        
        public boolean isEmpty() 
            synchronized (mutex) return m.isEmpty();
        
        public boolean containsKey(Object key) 
            synchronized (mutex) return m.containsKey(key);
        
        public boolean containsValue(Object value) 
            synchronized (mutex) return m.containsValue(value);
        
        public V get(Object key) 
            synchronized (mutex) return m.get(key);
        

        public V put(K key, V value) 
            synchronized (mutex) return m.put(key, value);
        
        public V remove(Object key) 
            synchronized (mutex) return m.remove(key);
        
        public void putAll(Map<? extends K, ? extends V> map) 
            synchronized (mutex) m.putAll(map);
        
        public void clear() 
            synchronized (mutex) m.clear();
        

        private transient Set<K> keySet;
        private transient Set<Map.Entry<K,V>> entrySet;
        private transient Collection<V> values;

        public Set<K> keySet() 
            synchronized (mutex) 
                if (keySet==null)
                    keySet = new SynchronizedSet<>(m.keySet(), mutex);
                return keySet;
            
        

        public Set<Map.Entry<K,V>> entrySet() 
            synchronized (mutex) 
                if (entrySet==null)
                    entrySet = new SynchronizedSet<>(m.entrySet(), mutex);
                return entrySet;
            
        

        public Collection<V> values() 
            synchronized (mutex) 
                if (values==null)
                    values = new SynchronizedCollection<>(m.values(), mutex);
                return values;
            
        

        public boolean equals(Object o) 
            if (this == o)
                return true;
            synchronized (mutex) return m.equals(o);
        
        public int hashCode() 
            synchronized (mutex) return m.hashCode();
        
        public String toString() 
            synchronized (mutex) return m.toString();
        

        // Override default methods in Map
        @Override
        public V getOrDefault(Object k, V defaultValue) 
            synchronized (mutex) return m.getOrDefault(k, defaultValue);
        
        @Override
        public void forEach(BiConsumer<? super K, ? super V> action) 
            synchronized (mutex) m.forEach(action);
        
        @Override
        public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) 
            synchronized (mutex) m.replaceAll(function);
        
        @Override
        public V putIfAbsent(K key, V value) 
            synchronized (mutex) return m.putIfAbsent(key, value);
        
        @Override
        public boolean remove(Object key, Object value) 
            synchronized (mutex) return m.remove(key, value);
        
        @Override
        public boolean replace(K key, V oldValue, V newValue) 
            synchronized (mutex) return m.replace(key, oldValue, newValue);
        
        @Override
        public V replace(K key, V value) 
            synchronized (mutex) return m.replace(key, value);
        
        @Override
        public V computeIfAbsent(K key,
                Function<? super K, ? extends V> mappingFunction) 
            synchronized (mutex) return m.computeIfAbsent(key, mappingFunction);
        
        @Override
        public V computeIfPresent(K key,
                BiFunction<? super K, ? super V, ? extends V> remappingFunction) 
            synchronized (mutex) return m.computeIfPresent(key, remappingFunction);
        
        @Override
        public V compute(K key,
                BiFunction<? super K, ? super V, ? extends V> remappingFunction) 
            synchronized (mutex) return m.compute(key, remappingFunction);
        
        @Override
        public V merge(K key, V value,
                BiFunction<? super V, ? super V, ? extends V> remappingFunction) 
            synchronized (mutex) return m.merge(key, value, remappingFunction);
        

        private void writeObject(ObjectOutputStream s) throws IOException 
            synchronized (mutex) s.defaultWriteObject();
        
    

SynchronizedMap 所做的可以概括为在输入Map 对象的主要方法中添加一个锁。所有被锁保护的方法不能被多个线程同时访问。这意味着像putget 这样的正常操作可以由单个线程同时对Map 对象中的所有数据执行。

它现在使Map 对象线程安全,但在某些情况下性能可能会成为问题。

ConcurrentMap的实现要复杂得多,具体可以参考Building a better HashMap。简而言之,它的实现同时考虑了线程安全和性能。

【讨论】:

【参考方案18】:

同步地图:

Synchronized Map 也与 Hashtable 没有太大区别,并且在并发 Java 程序中提供类似的性能。 Hashtable 和 SynchronizedMap 之间的唯一区别是 SynchronizedMap 不是遗留的,您可以使用 Collections.synchronizedMap() 方法包装任何 Map 以创建它的同步版本。

ConcurrentHashMap:

ConcurrentHashMap 类提供标准 HashMap 的并发版本。这是对 Collections 类中提供的 synchronizedMap 功能的改进。

与 Hashtable 和 Synchronized Map 不同,它从不锁定整个 Map,而是将 Map 划分为段并在这些段上进行锁定。如果读取线程的数量大于写入线程的数量,它的性能会更好。

ConcurrentHashMap 默认分为 16 个区域并应用锁。可以在初始化 ConcurrentHashMap 实例时设置此默认数字。在特定段中设置数据时,将获得该段的锁定。这意味着如果两个更新都影响单独的存储桶,它们仍然可以安全地同时执行,从而最大限度地减少锁争用,从而最大限度地提高性能。

ConcurrentHashMap 不会抛出 ConcurrentModificationException

如果一个线程尝试修改它而另一个线程正在对其进行迭代,则 ConcurrentHashMap 不会抛出 ConcurrentModificationException

synchornizedMap 和 ConcurrentHashMap 的区别

Collections.synchornizedMap(HashMap) 将返回一个几乎等同于 Hashtable 的集合,其中对 Map 的每个修改操作都锁定在 Map 对象上,而在 ConcurrentHashMap 的情况下,通过将整个 Map 划分为基于不同的分区来实现线程安全在并发级别上并且只锁定特定部分而不是锁定整个 Map。

ConcurrentHashMap 不允许空键或空值,而同步 HashMap 允许一个空键。

类似链接

Link1

Link2

Performance Comparison

【讨论】:

【参考方案19】:

ConcurrentHashMap 作为并发包的一部分在 Java 1.5 中作为 Hashtable 的替代品出现。使用 ConcurrentHashMap 不仅可以在并发多线程环境中安全使用,而且提供比 Hashtable 和 synchronizedMap 更好的性能,您有更好的选择。 ConcurrentHashMap 性能更好,因为它锁定了 Map 的一部分。它允许并发读取操作,同时通过同步写入操作来保持完整性。

ConcurrentHashMap是如何实现的

ConcurrentHashMap 是作为 Hashtable 的替代品而开发的,它以额外的能力支持 Hashtable 的所有功能,即所谓的并发级别。 ConcurrentHashMap 允许多个读取器同时读取而不使用块。通过将 Map 分成不同的部分并在更新中仅阻止 Map 的一部分,这成为可能。默认情况下,并发级别为 16,因此 Map 被拆分为 16 个部分,每个部分由单独的块管理。这意味着,如果它们与 Map 的不同部分一起工作,则 16 个线程可以同时与 Map 一起工作。它使 ConcurrentHashMap 高效,并且不会降低线程安全性。

如果你对 ConcurrentHashMap 的一些重要特性感兴趣,以及何时应该使用 Map 的这种实现——我只是放了一篇好文章的链接——How to use ConcurrentHashMap in Java

【讨论】:

以上是关于ConcurrentHashMap 和 Collections.synchronizedMap(Map) 有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章

ConcurrentHashMap1.7和ConcurrentHashMap1.8的异同

ConcurrentHashMap1.7和ConcurrentHashMap1.8的异同

ConcurrentHashMap1.7和ConcurrentHashMap1.8的异同

java ConcurrentHashMap 初识

Java中的ConcurrentHashMap和Hashtable [重复]

HashMap和ConcurrentHashMap实现原理及源码分析