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


607

我有一个地图,该地图将同时被多个线程修改。

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

  • Hashtable
  • Collections.synchronizedMap(Map)
  • ConcurrentHashMap

据我了解,Hashtable是一个旧的实现(扩展了过时的Dictionary类),后来对其进行了修改以适合该Map接口。虽然它同步的,但似乎存在严重的可伸缩性问题,因此不建议用于新项目。

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


7
@SmilesinaJar链接当前已断开,这里是本文的存档副本:为什么ConcurrentHashMap比Hashtable更好并且与HashMap一样好
informatik01


仅供参考,Java 6 ConcurrentSkipListMap是另一个线程安全的Map实现。使用Skip List算法设计为在负载下高度并发。
罗勒·布尔克

Answers:


423

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

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


8
从源代码
来看

123
另请注意,ConcurrentHashMap不允许使用空键或空值。因此,它们不是同步映射的同等替代方案。
2011年


5
在那篇文章中提出@AbdullahShaikh的问题已在Java 7中,并进一步改进已在Java中8发
pulse0ne

5
@hengxin:执行包含多个查询或更新地图的操作时,或者在遍历地图时,必须手动在地图上进行同步以确保一致性。同步的映射仅保证对映射上的单个操作(方法调用)的一致性,这使它变得一无是处,因为大多数现实生活中的操作都是微不足道的,因此您仍然必须手动进行同步。
Holger

241
╔═══════════════╦═══════════════════╦═══════════════════╦═════════════════════╗
║   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 锁定对象,而ConcurrentHashMap锁定存储桶


13
Hashtable不是地图的锁定部分。看一下实现。它使用synchronized没有提供锁的键,因此基本上意味着它hashtable在每次操作中都将整个锁锁定。
RMachnik

6
那syncedMap呢?
塞缪尔·埃德温·沃德

3
Collections.syncronizedMap的行为类似于支持映射,不同之处在于所有方法都是线程安全的
Sergii Shevchyk

5
我会打印这张桌子,然后以每张5美元的价格出售;)。好人@shevchyk
realPK

编辑:都不是完全线程安全的。对于新开发者来说,这有点误导。请参阅:ibm.com/developerworks/java/library/j-jtp07233/index.html以了解即使ConcurrentHashMap在外部数据争夺中也不是完全线程安全的。(例如:1个线程删除了一个值,另一个线程稍后尝试检查该值是否存在,如果不存在,则将其放入。这是数据竞争的条件,并且仍然意味着,尽管使用了“ ConcurrentHashMap”,您也无法缓解所有线程安全问题。
僵尸

142

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

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

ConcurrentHashMap使用非常复杂的技术,以减少同步的需要,并允许由多个线程并行读取访问,而无需同步,更重要的是,提供了Iterator一个无需同步,甚至允许地图将互为作用时(修改虽然它不保证是否不会返回在迭代过程中插入的元素)。


4
现在这就是我想要的!:)没有同步的Iterator仅仅是纯甜蜜的!谢谢你的信息!:)(:
Kounavi 2011年

很好的答案..但这是否意味着在读取线程期间,由于读取器线程不同步,因此不会获得最新更新。
MrA

@MrA:您是否在询问ConcurrentHashMap?您所说的“检索”是什么意思?
Michael Borgwardt

4
@Michael Borgwardt,例如ConcurrentHashmap。假设有多个线程。其中一些正在更新地图,而另一些正在从同一地图获取数据。因此,在这种情况下,当线程尝试读取时,可以确保它们将获取已更新的最新数据,因为读取器线程不必持有锁。
MrA

35

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

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

我找到了一个博客条目,该条目复制了我强烈推荐的出色著作Java Concurrency In Practice中的表格。

只有在需要包装具有其他特征的地图(例如某种有序地图,例如TreeMap)时,Collections.synchronizedMap才有意义。


2
是的-似乎我在所有其他答案中都提到了这本书!
比尔·米歇尔

@BillMichell链接已断开

@Govinda访问链接之前,请关闭JavaScript。博客条目仍然存在!
比尔·米歇尔

32

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

另外一个不同之处是,它ConcurrentHashMap不会保留传入的Map中元素的顺序。这类似于HashMap存储数据时的顺序。不能保证保留元素顺序。虽然Collections.synchronizedMap()将保留传入的Map的元素顺序。例如,如果将传递TreeMapConcurrentHashMap,则中的元素顺序ConcurrentHashMap可能与中的顺序不同TreeMap,但Collections.synchronizedMap()会保留顺序。

此外,ConcurrentHashMap可以确保ConcurrentModificationException在一个线程正在更新映射而另一线程正在遍历从映射获得的迭代器时不引发任何异常。但是,Collections.synchronizedMap()对此不能保证。

一篇文章展示了这两者的差异,也展示了两者的差异ConcurrentSkipListMap


13

同步图:

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

ConcurrentHashMap:

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

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

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

ConcurrentHashMap不会引发ConcurrentModificationException

如果一个线程试图修改它而另一个线程对其进行迭代,则ConcurrentHashMap不会引发ConcurrentModificationException。

synchornizedMap和ConcurrentHashMap之间的区别

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

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

相似链接

链接1

链接2

性能比较




9

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

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

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

但是,不要认为这ConcurrentHashMapHashMap具有上述典型synchronized块的a的简单替代方案。阅读文章,了解其复杂性更好。


7

这里有几个:

1)ConcurrentHashMap仅锁定Map的一部分,而SynchronizedMap锁定整个MAp。
2)ConcurrentHashMap具有优于SynchronizedMap的性能,并且具有更高的可伸缩性。
3)如果有多个读取器和单个写入器,ConcurrentHashMap是最佳选择。

这段文字来自Java中ConcurrentHashMap和hashtable之间的区别


7

我们可以通过使用ConcurrentHashMap和syncnisedHashmap和Hashtable来实现线程安全。但是,如果看一下它们的体系结构,会有很多不同。

  1. syncHashmap和Hashtable

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

  1. 并发哈希图

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

如果线程1要在段2中执行放置操作,而线程2要在段4中执行放置操作,则此处允许。意味着16个线程可以一次在ConcurrentHashMap上执行update(put / delete)操作。

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


1
,1。如果多个线程尝试编辑相同的块会发生什么?2.如果说两个线程试图从同一块中读取数据,而另一个线程如果同时写入数据,那又会怎样呢?
prnjn

6

并发哈希图

  • 当项目中需要很高的并发时,应使用ConcurrentHashMap。
  • 它是线程安全的,无需同步整个映射。
  • 使用锁定完成写入时,读取会非常快。
  • 在对象级别没有锁定。
  • 在哈希图存储桶级别,锁定的粒度要精细得多。
  • 如果一个线程试图修改它,而另一个线程对其进行迭代,则ConcurrentHashMap不会引发ConcurrentModificationException。
  • ConcurrentHashMap使用多个锁。

SynchronizedHashMap

  • 对象级别的同步。
  • 每个读/写操作都需要获取锁定。
  • 锁定整个集合是性能开销。
  • 本质上,这仅允许访问整个地图的一个线程,并阻止所有其他线程。
  • 它可能引起争用。
  • SynchronizedHashMap返回Iterator,并发修改后快速失败。

资源


4

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

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


4

除了它提供的并发功能外,还有一个关键功能要注意ConcurrentHashMap,那就是故障安全迭代器。我看到开发人员ConcurrentHashMap之所以使用它们,只是因为他们要编辑条目集-遍历条目集时将其删除。 Collections.synchronizedMap(Map)不提供故障安全迭代器,而是提供故障快速迭代器。快速失败迭代器使用映射大小的快照,该快照在迭代过程中无法编辑。


3
  1. 如果数据一致性非常重要-请使用Hashtable或Collections.synchronizedMap(Map)。
  2. 如果速度/性能非常重要,并且可能会损害数据更新,请使用ConcurrentHashMap。

2

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


1

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

在ConcurrentHashMap中,同步的方式略有不同。ConcurrentHashMap不会将每个方法都锁定在一个公共锁上,而是将单独的锁用于单独的存储桶,从而仅锁定Map的一部分。默认情况下,有16个存储桶,还为单独的存储桶提供单独的锁。因此,默认的并发级别为16。这意味着从理论上讲,如果给定的时间,则16个线程都可以访问ConcurrentHashMap(如果它们全部都将进入单独的存储桶)。


1

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

如何实现ConcurrentHashMap

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

如果您对ConcurrentHashMap的一些重要功能感兴趣,以及何时应使用Map的这种实现-我只是将链接指向了一篇不错的文章- 如何在Java中使用ConcurrentHashMap


0

除了建议的内容外,我还要发布与相关的源代码SynchronizedMap

为了使Map线程安全,我们可以使用Collections.synchronizedMapstatement并输入map实例作为参数。

synchronizedMapin 的实现Collections如下

   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的对象。锁保护的所有方法不能同时被多个线程访问。这意味着可以对对象中的所有数据执行相同的正常操作,put并且get可以由单个线程同时执行Map

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

ConcurrentMap在执行要复杂得多,我们可以参考构建一个更好的HashMap了解详情。简而言之,它的实现考虑了线程安全性和性能。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.