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

我有一个映射,这是由几个线程并发修改。

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

  • # EYZ0
  • # EYZ0
  • # EYZ0

根据我的理解,Hashtable是一个旧的实现(扩展了过时的Dictionary类),后来它被改编以适应Map接口。虽然它是同步的,但它似乎有严重的可伸缩性问题,不鼓励新项目。

那另外两个呢?Collections.synchronizedMap(Map)ConcurrentHashMaps返回的map有什么不同?哪一种适合哪种情况?

346266 次浏览

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

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

如果可以使用ConcurrentHashMap,则首选它——尽管它至少需要Java 5。

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

我找到了一个博客条目,它复制了优秀的书Java并发实践中的一个表,我强烈推荐这本书。

集合。synchronizedMap只有在需要用其他特征(可能是某种有序映射,如TreeMap)来包装映射时才有意义。

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

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

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

你对HashTable的看法是正确的,你可以忘记它。

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

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

但是,不要认为ConcurrentHashMap是上面所示的典型的synchronized块的HashMap的简单替代品。阅读文章以更好地理解其复杂性。

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

当您有简单的插入和查找时,这不是什么大问题(除非您做得非常密集),但是当您需要遍历整个Map时,这就变成了一个大问题,对于一个大型Map来说,这可能会花费很长时间——当一个线程这样做时,所有其他线程都必须等待,如果它们想要插入或查找任何东西。

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

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

以下是一些例子:

1) ConcurrentHashMap只锁定Map的一部分,而SynchronizedMap锁定整个Map 2) ConcurrentHashMap比SynchronizedMap性能更好,可伸缩性更强 3)在多读取器和单写入器的情况下,ConcurrentHashMap是最好的选择

这段文字来自ConcurrentHashMap和Java哈希表的区别

ConcurrentHashMap中,锁应用于一个段而不是整个Map。 每个段管理自己的内部哈希表。该锁仅应用于更新操作。Collections.synchronizedMap(Map)同步整个映射
  1. 如果数据一致性非常重要-使用Hashtable或Collections.synchronizedMap(Map)。
  2. 如果速度/性能非常重要,数据更新可能会受到影响-使用ConcurrentHashMap。
╔═══════════════╦═══════════════════╦═══════════════════╦═════════════════════╗
║   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锁定只有水桶.

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

另外一个不同之处在于,ConcurrentHashMap将不会保留传入的Map中元素的顺序。在存储数据时,它类似于HashMap。不能保证元素顺序被保留。而Collections.synchronizedMap()将保留传入Map的元素顺序。例如,如果您将TreeMap传递给ConcurrentHashMapConcurrentHashMap中的元素顺序可能与TreeMap中的元素顺序不相同,但Collections.synchronizedMap()将保留该顺序。

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

一个帖子ConcurrentSkipListMap说明了两者的区别。

ConcurrentHashMap

  • ConcurrentHashMap用于性能关键型应用程序,其中写操作远远多于读操作。
  • 它是线程安全的,无需同步整个映射。
  • 当使用锁进行写操作时,读操作可以非常快地进行。
  • 在对象级别上没有锁定。
  • 在hashmap bucket级别上,锁定的粒度要细得多。
  • 如果一个线程试图修改它,而另一个线程正在遍历它,ConcurrentHashMap不会抛出ConcurrentModificationException。
  • ConcurrentHashMap使用大量锁。
  • 读操作是非阻塞的,而写操作对特定的段或桶进行锁定。

SynchronizedHashMap

  • 对象级同步。
  • 每个读/写操作都需要获得锁。
  • 锁定整个集合是一种性能开销。
  • 这本质上给了一个线程访问整个映射&阻塞所有其他线程。
  • 这可能会引起争论。
  • SynchronizedHashMap返回迭代器,它在并发修改时快速失败。

Collection.synchronizedMap ()

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

source

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

在ConcurrentHashMap中,同步的方式略有不同。ConcurrentHashMap对不同的bucket使用不同的锁,从而只锁定Map的一部分,而不是将每个方法锁定在一个公共锁上。 默认情况下,有16个桶,并且为不同的桶提供不同的锁。所以默认的并发级别是16。这意味着理论上在任何给定的时间都有16个线程可以访问ConcurrentHashMap,如果它们都要分开的桶
ConcurrentHashMap除了提供并发特性之外,还有一个关键特征需要注意,这是自动防故障装置迭代器。我见过开发人员使用ConcurrentHashMap只是因为他们想编辑条目集-在迭代时放置/删除它。 Collections.synchronizedMap(Map)没有提供自动防故障装置迭代器,而是提供了快速失败迭代器。快速失败迭代器使用map大小的快照,在迭代过程中不能编辑

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

  1. synchronisedHashmap和Hashtable

两者都将在对象级别上维护锁。所以如果你想执行任何操作,比如put/get,那么你必须先获得锁。同时,其他线程不允许执行任何操作。所以在同一时间,只有一个线程可以操作这个。所以这里的等待时间会增加。我们可以说,与ConcurrentHashMap相比,性能相对较低。

  1. ConcurrentHashMap

它将在段级维护锁。它有16个分段,并发级别默认为16。因此,一次可以有16个线程操作ConcurrentHashMap。而且,读操作不需要锁。所以任何数量的线程都可以对它执行get操作。

如果thread1想要在段2中执行put操作,而thread2想要在段4中执行put操作,那么在这里是允许的。意味着16个线程可以同时对ConcurrentHashMap执行更新(放置/删除)操作。

这样在这里等待的时间就会少一些。因此,性能相对于synchronisedHashmap和Hashtable更好。

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

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

synchronizedMapCollections中的实现如下所示

   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在实现中要复杂得多,我们可以参考构建更好的HashMap了解详细信息。简而言之,它的实现同时考虑了线程安全和性能。

同步地图:

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

ConcurrentHashMap:

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

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

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

ConcurrentHashMap不会抛出ConcurrentModificationException异常

如果一个线程试图修改它,而另一个线程正在遍历它,ConcurrentHashMap不会抛出ConcurrentModificationException

synchronizedmap和ConcurrentHashMap的区别

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

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

类似的链接

Link1

Link2

性能比较 .

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

ConcurrentHashMap是如何实现的

ConcurrentHashMap是作为哈希表的替代品开发的,支持哈希表的所有功能,并具有额外的能力,因此被称为并发级。ConcurrentHashMap允许多个阅读器同时读取而不使用块。通过将Map分离到不同的部分,并在更新中只阻塞部分Map,这是可能的。默认情况下,并发级别为16,因此Map被划分为16个部分,每个部分由分开的块管理。这意味着,如果16个线程使用Map的不同部分,那么它们可以同时使用Map。它使ConcurrentHashMap高度高效,并且不会降低线程安全性。

如果你对ConcurrentHashMap的一些重要特性感兴趣,以及什么时候应该使用这种Map实现——我只是放了一个链接到一篇好文章——如何在Java中使用ConcurrentHashMap