代码之家  ›  专栏  ›  技术社区  ›  Henning

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

  •  564
  • Henning  · 技术社区  · 17 年前

    我有一个映射,它将由多个线程同时修改。

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

    • Hashtable
    • Collections.synchronizedMap(Map)
    • ConcurrentHashMap

    是一个旧的实现(扩展了过时的 Dictionary 类),以后进行了调整以适应 Map 界面当它 scalability issues 不鼓励新项目。

    集合.同步地图(地图) 和 ConcurrentHashMap s哪一种适合哪种情况?

    18 回复  |  直到 10 年前
        1
  •  437
  •   Yuval Adam    17 年前

    根据您的需要,使用 ConcurrentHashMap . 它允许从多个线程并发修改映射,而无需阻止它们。 Collections.synchronizedMap(map)

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

        2
  •  250
  •   Zombies    6 年前
    ╔═══════════════╦═══════════════════╦═══════════════════╦═════════════════════╗
    ║   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   ║ 
    ╚═══════════════╩═══════════════════════════════════════╩═════════════════════╝
    

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

        3
  •  143
  •   Michael Borgwardt    17 年前

    Hashtable Collections.synchronizedMap(Map)

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

    ConcurrentHashMap 使用非常复杂的技术来减少同步的需要,允许多线程在不进行同步的情况下进行并行读取访问,更重要的是,提供了 Iterator

        4
  •  35
  •   Ahmed Ashour chim    11 年前

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

    另一个区别是 ConcurrentHashMap HashMap 在存储数据时。不能保证元素顺序得到保留。虽然 Collections.synchronizedMap() 将保留传入的映射的元素顺序。例如,如果您通过 TreeMap 到 ConcurrentHashMap ,元素在 可能与中的顺序不同 但是 Collections.synchronizedMap() 这将维持秩序。

    此外 可以保证没有 ConcurrentModificationException 当一个线程更新映射,另一个线程遍历从映射获得的迭代器时抛出。然而 Collections.synchronizedMap()

    有 one post ConcurrentSkipListMap .

        5
  •  35
  •   SkyWalker    9 年前

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

    它被设计成在多线程使用时可以很好地伸缩。当一次只有一个线程访问Map时,性能可能会稍差,但当多个线程同时访问Map时,性能会显著提高。

    blog entry 那是从那本好书中复制出来的一张桌子 Java Concurrency In Practice ,我极力推荐。

    只有当您需要使用其他一些特征(可能是某种有序映射,如树形映射)来包装映射时,Collections.synchronizedMap才有意义。

        6
  •  14
  •   Ramesh Papaganti    8 年前

    同步映射:

    ConcurrentHashMap:

    与哈希表和同步映射不同,它从不锁定整个映射,而是将映射划分为段,并对这些段执行锁定。如果读线程的数量大于写线程的数量,它的性能会更好。

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

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

    synchornizedMap和ConcurrentHashMap之间的差异

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

    类似链接

    Link1

    Link2

    Performance Comparison

        7
  •  13
  •   Bill the Lizard    13 年前

    在里面 ConcurrentHashMap Collections.synchronizedMap(Map) 同步整个地图。

        8
  •  12
  •   Zach Scrivena    17 年前

        9
  •  9
  •   eljenso    17 年前

    你说得对 HashTable ,你可以忘了它。

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

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

    ConcurrentHashMap 是一个简单的选择 HashMap synchronized this 本文旨在更好地理解其复杂性。

        10
  •  7
  •   Raj    14 年前


    2) ConcurrentHashMap比SynchronizedMap具有更好的性能和更大的可扩展性。
    3) 在多个读卡器和单个写卡器的情况下,ConcurrentHashMap是最佳选择。

    此文本来自 Difference between ConcurrentHashMap and hashtable in Java

        11
  •  7
  •   Sumanth Varada    9 年前

    1. 同步hashmap和Hashtable

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

    1. ConcurrentHashMap

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

    如果thread1希望在段2中执行put操作,thread2希望在段4上执行put操作,则此处允许。意味着,16个线程一次可以对ConcurrentHashMap执行更新(put/delete)操作。

        12
  •  6
  •   Premraj    5 年前

    ConcurrentHashMap

    • ConcurrentHashMap适用于写操作远多于读操作的性能关键型应用程序。
    • 它是线程安全的,无需同步整个映射。
    • 在对象级别没有锁定。
    • 如果一个线程试图修改ConcurrentHashMap,而另一个线程正在对其进行迭代,则ConcurrentHashMap不会抛出ConcurrentModificationException。
    • 读操作是非阻塞的,而写操作锁定特定的段或存储桶。

    • 对象级别的同步。
    • 每个读/写操作都需要获取锁。
    • 这本质上只允许访问整个地图的一个线程&阻止所有其他线程。
    • 这可能会引起争论。
    • SynchronizedHashMap返回迭代器,该迭代器在并发修改时快速失败。

    Collection.synchronizedMap()

    • Collections实用程序类提供对集合进行操作并返回包装的集合的多态算法。其synchronizedMap()方法提供线程安全功能。

    source

        13
  •  4
  •   starblue    17 年前

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

        14
  •  4
  •   realPK    9 年前

    有 一个关键特征 注意 ConcurrentHashMap 除了它提供的并发特性之外 故障保险 只是因为他们想在迭代时编辑entryset-put/remove。 Collections.synchronizedMap(Map) 不提供 迭代器,但它提供 快速失败 取而代之的是迭代器。fail fast迭代器使用在迭代期间无法编辑的映射大小的快照。

        15
  •  3
  •   Christophe Roussy    9 年前
    1. 如果速度/性能非常重要,并且数据更新可能受到影响,请使用ConcurrentHashMap。
        16
  •  2
  •   Kounavi    15 年前

    通常,如果要使用 ConcurrentHashMap 确保您准备好错过“更新”
    (即,打印HashMap的内容并不确保它将打印最新的地图)并使用API,如 CyclicBarrier 确保整个程序生命周期的一致性。

        17
  •  1
  •   Brad Larson    9 年前

    在ConcurrentHashMap中,同步的方式略有不同。ConcurrentHashMap没有将每个方法都锁定在一个公共锁上,而是对单独的存储桶使用单独的锁,从而只锁定映射的一部分。

        18
  •  1
  •   Oleg Poltoratskii    6 年前

    ConcurrentHashMap在Java1.5中作为Hashtable的替代方案作为并发包的一部分提供。使用ConcurrentHashMap,您不仅可以在并发多线程环境中安全地使用它,而且还可以提供比Hashtable和synchronizedMap更好的性能,这是一个更好的选择。ConcurrentHashMap性能更好,因为它锁定了Map的一部分。它允许并发读操作,同时通过同步写操作保持完整性。

    ConcurrentHashMap是如何实现的

    ConcurrentHashMap是作为Hashtable的替代品开发的,它支持Hashtable的所有功能,并具有额外的能力,即所谓的并发级别。ConcurrentHashMap允许多个读卡器在不使用块的情况下同时读取。通过将地图分为不同的部分,并在更新中仅阻止地图的一部分,这就成为可能。默认情况下,并发级别为16,所以映射分为16个部分,每个部分由单独的块管理。这意味着,如果16个线程处理Map的不同部分,那么它们可以同时处理Map。它使ConcurrentHashMap具有更高的生产效率,而不是降低线程安全性。

    如果您对ConcurrentHashMap的一些重要特性感兴趣,并且您应该在什么时候使用这个Map的实现,那么我只提供了一个指向一篇好文章的链接- How to use ConcurrentHashMap in Java

        19
  •  0
  •   Eugene    8 年前

    除了建议之外,我还想发布与此相关的源代码 SynchronizedMap .

    作出决定 Map 线程安全,我们可以使用 Collections.synchronizedMap

    实施 synchronizedMap 在里面 Collections 就像下面一样

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

    地图 对象由 同步地图 对象
    让我们深入研究 同步地图 ,

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

    同步地图 DO可以概括为向输入的主要方法添加一个锁 地图 put 和 get 地图 对象

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

    ConcurrentMap 在实现上要复杂得多,我们可以参考 Building a better HashMap