HashMap和ConcurrentHashMap区别详解
HashMap和ConcurrentHashMap区别详解
HashMap和ConcurrentHashMap都是Java中常用的映射(Map)实现类,它们之间有一些显著的区别,主要体现在线程安全性、性能和使用场景上。
1.线程安全性
- HashMap:不是线程安全的,在多个线程并发访问时可能会导致数据不一致或程序崩溃。例如,如果多个线程同时修改HashMap中的元素,可能会导致
ConcurrentModificationException
,或者导致数据丢失、覆盖等问题。如果需要在多线程环境中使用HashMap,需要手动同步。例如,可以使用Collections.synchronizedMap()
或在外部进行同步。
- ConcurrentHashMap:是线程安全的,专门设计用于并发环境。它通过分段锁(Segment Locking)来提高并发访问性能。在ConcurrentHashMap中,锁的粒度较小,不同的线程可以同时访问不同的桶(bucket),从而减少锁的争用,提高性能。ConcurrentHashMap允许多个线程同时读操作,且不需要加锁。对于写操作,ConcurrentHashMap使用分段锁来确保线程安全。
2.锁的机制
HashMap:没有内置的锁机制,所有操作(如插入、更新、删除)必须由外部进行同步。
ConcurrentHashMap:采用分段锁(Segment Locks)或桶锁(Bucket Locks)机制。在Java 8及之后的版本中,ConcurrentHashMap引入了更加细粒度的锁机制,使用了
synchronized
和CAS(Compare-And-Swap)来保证线程安全,而不是整个Map上加锁。写操作对于每个桶或者段采用不同的锁,这样不同的线程可以同时在不同的段上进行修改。读操作不需要加锁,因此能够在多线程环境下实现高效并发读取。
3.性能差异
HashMap:在单线程环境下,HashMap性能较好,因为它不需要额外的同步开销。
ConcurrentHashMap:在多线程环境下,ConcurrentHashMap由于采用了分段锁和更细粒度的锁机制,性能要优于同步的HashMap。但是,由于需要保证线程安全,它的性能开销也要比HashMap略高,尤其是在低并发情况下,性能差异不大。
4.支持的操作
HashMap:提供基本的
put()
、get()
、remove()
等操作,不支持并发时的额外操作。ConcurrentHashMap:除了
put()
、get()
、remove()
等常规操作外,还提供一些并发友好的操作:putIfAbsent(K key, V value)
:如果给定的键还没有映射值,则添加。replace(K key, V oldValue, V newValue)
:只有当键映射的值与oldValue
相等时,才会替换成newValue
。computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)
:当指定键没有映射值时,可以使用给定的函数计算并添加。
5.Null值支持
HashMap:HashMap可以存储null作为键和值。
ConcurrentHashMap:ConcurrentHashMap不允许存储null键或值。如果尝试存储null,会抛出
NullPointerException
。
6.适用场景
HashMap:适用于单线程场景,或者在多线程场景下你手动管理同步。如果不需要线程安全性,HashMap通常更快。
ConcurrentHashMap:适用于多线程并发读写的场景,尤其是当多个线程频繁地访问和修改集合中的数据时。在并发性能要求较高时,ConcurrentHashMap比HashMap更适合。
总结
- HashMap是一个非线程安全的实现,适合单线程或外部同步的情况。
- ConcurrentHashMap是一个线程安全的实现,适用于高并发的多线程环境,它通过分段锁和更细粒度的锁来保证线程安全,从而提高并发性能。如果你在多线程环境中使用Map,而且不想自己手动同步,那么ConcurrentHashMap是更好的选择。