首页 文章

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

提问于
浏览
554

我有一个Map,它将被多个线程同时修改 .

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

  • Hashtable

  • Collections.synchronizedMap(Map)

  • ConcurrentHashMap

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

但是其他两个怎么样? Collections.synchronizedMap(Map)ConcurrentHashMap 返回的 Map 之间有什么区别?哪一种适合哪种情况?

18 回答

  • 7

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

  • 216

    我们可以使用ConcurrentHashMap和synchronisedHashmap和Hashtable来实现线程安全 . 但是如果你看一下他们的架构,会有很多不同 .

    • synchronisedHashmap and Hashtable

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

    • ConcurrentHashMap

    它将保持锁定在段级别 . 它有16个段,默认情况下将并发级别维持在16 . 所以一次,16个线程可以在ConcurrentHashMap上运行 . 而且,读操作不需要锁定 . 因此任何数量的线程都可以对其执行get操作 . 如果thread1想要在段2中执行put操作并且thread2想要在段4上执行put操作,那么这里允许它 . 意味着,16个线程可以一次对ConcurrentHashMap执行更新(put / delete)操作 . 这样等待时间会减少 . 因此,性能相对于synchronisedHashmap和Hashtable相对更好 .

  • 12

    Synchronized Map:

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

    ConcurrentHashMap:

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

    与Hashtable和Synchronized Map不同,它永远不会锁定整个Map,而是将段划分为段,并对其进行锁定 . 如果读取器线程的数量大于写入器线程的数量,则它的性能会更好 .

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

    ConcurrentHashMap不会抛出ConcurrentModificationException

    如果一个线程试图修改它而另一个线程迭代它,则ConcurrentHashMap不会抛出ConcurrentModificationException

    Difference between synchornizedMap and ConcurrentHashMap

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

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

    Similar links

    Link1

    Link2

    Performance Comparison

  • 133

    你是对的 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文章,以更好地了解其复杂性 .

  • 5

    ConcurrentHashMap

    • 在项目中需要非常高的并发性时,应该使用ConcurrentHashMap .

    • 没有同步整个 Map 是线程安全的 .

    • 使用锁完成写操作时,读取速度可能非常快 .

    • 对象级别没有锁定 .

    • 在hashmap桶级别,锁定的粒度更精细 .

    • 如果一个线程试图修改它而另一个线程迭代它,则ConcurrentHashMap不会抛出ConcurrentModificationException .

    • ConcurrentHashMap使用大量锁 .

    SynchronizedHashMap

    • 对象级别的同步 .

    • 每次读/写操作都需要获取锁 .

    • 锁定整个集合是一种性能开销 .

    • 这本质上只允许访问整个 Map 的一个线程并阻止所有其他线程 .

    • 可能会引起争用 .

    • SynchronizedHashMap返回Iterator,它在并发修改时失败 .

    source

  • 31

    一般来说,如果你想使用 ConcurrentHashMap ,请确保你准备好错过'updates'
    (即打印HashMap的内容并不能确保它会打印最新的Map)并使用像 CyclicBarrier 这样的API来确保整个程序生命周期的一致性 .

  • 1

    Hashtable for HashtableCollections.synchronizedMap(Map) 中以完全相同的方式存在 - 它们使用非常简单的同步,这意味着只有一个线程可以同时访问 Map .

    当你有简单的插入和查找时(除非你非常密集地进行),这不是什么大问题,但当你需要迭代整个Map时会出现一个大问题,这可能需要很长时间才能完成一个大的Map - 而一个线程执行此操作,所有其他线程必须等待,如果他们想要插入或查找任何东西 .

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

  • 11

    这里有几个:

    1)ConcurrentHashMap仅锁定Map的一部分,但SynchronizedMap锁定整个MAp .
    2)ConcurrentHashMap比SynchronizedMap具有更好的性能,并且更具可伸缩性 .
    3)如果是多个读者和单个编写者,ConcurrentHashMap是最佳选择 .

    本文来自Difference between ConcurrentHashMap and hashtable in Java

  • 33

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

    访问不会锁定整个 Map ,而是使用更精细的策略,从而提高可扩展性 . 还存在专门用于并发访问的功能性增强,例如,并发迭代器 .

  • 387

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

  • 5

    除了建议之外,我还想发布与 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了解详情 . 简而言之,它的实现考虑了线程安全和性能 .

  • 9

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

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

  • 0

    除了它提供的并发功能之外, one critical feature 还要注意 ConcurrentHashMap ,这是 fail-safe iterator . 我见过开发人员使用 ConcurrentHashMap 只是因为他们想要编辑入口集 - 在迭代时放置/删除 . Collections.synchronizedMap(Map) 不提供 fail-safe 迭代器,但它提供 fail-fast 迭代器 . 失败快速迭代器使用迭代期间无法编辑的映射大小的快照 .

  • 7

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

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

    我找到了blog entry,它从优秀的书Java Concurrency In Practice中再现了一张 table ,我完全推荐 .

    Collections.synchronizedMap真正有意义,只有当你需要包含一些具有其他特征的 Map 时,可能是某种有序的 Map ,比如TreeMap .

  • 4

    根据您的需要,使用 ConcurrentHashMap . 它允许从多个线程并发地修改Map,而无需阻止它们 . Collections.synchronizedMap(map) 创建一个阻塞Map,这会降低性能,尽管确保一致性(如果使用得当) .

    如果需要确保数据一致性,请使用第二个选项,并且每个线程都需要具有最新的 Map 视图 . 如果性能至关重要,请使用第一个,并且每个线程仅将数据插入到 Map 中,读取发生的频率较低 .

  • 0
    ╔═══════════════╦═══════════════════╦═══════════════════╦═════════════════════╗
    ║   Property    ║     HashMap       ║    Hashtable      ║  ConcurrentHashMap  ║
    ╠═══════════════╬═══════════════════╬═══════════════════╩═════════════════════╣ 
    ║      Null     ║     allowed       ║              not allowed                ║
    ║  values/keys  ║                   ║                                         ║
    ╠═══════════════╬═══════════════════╬═════════════════════════════════════════╣
    ║Is thread-safe ║       no          ║                  yes                    ║
    ╠═══════════════╬═══════════════════╬═══════════════════╦═════════════════════╣
    ║     Lock      ║       not         ║ locks the whole   ║ locks the portion   ║        
    ║  mechanism    ║    applicable     ║       map         ║                     ║ 
    ╠═══════════════╬═══════════════════╩═══════════════════╬═════════════════════╣
    ║   Iterator    ║               fail-fast               ║ weakly consistent   ║ 
    ╚═══════════════╩═══════════════════════════════════════╩═════════════════════╝
    

    关于锁定机制: Hashtable locks the object,而 ConcurrentHashMap 锁定only the bucket .

  • 2
    • 如果数据一致性非常重要 - 使用Hashtable或Collections.synchronizedMap(Map) .

    • 如果速度/性能非常重要且数据更新可能受到影响 - 请使用ConcurrentHashMap .

  • 2

    这两者之间的主要区别在于 ConcurrentHashMap 将仅锁定正在更新的部分数据,而其他部分数据可以被其他线程访问 . 但是, Collections.synchronizedMap() 将在更新时锁定所有数据,其他线程只能在释放锁时访问数据 . 如果有许多更新操作和相对少量的读取操作,则应选择 ConcurrentHashMap .

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

    此外, ConcurrentHashMap 可以保证在一个线程更新映射而另一个线程正在遍历从映射获得的迭代器时,没有抛出 ConcurrentModificationException . 但是, Collections.synchronizedMap() 无法保证 .

    one post证明了这两者的区别以及 ConcurrentSkipListMap .

相关问题