HashMap和ConcurrentHashMap区别

305 阅读4分钟

HashMap和ConcurrentHashMap区别

1. 线程安全性

HashMapConcurrentHashMap 的最大区别在于线程安全性。

  • HashMap 是非线程安全的。这意味着在多线程环境下,如果多个线程同时修改 HashMap,可能会导致数据不一致,甚至引发死循环等问题。
    • 在 JDK 1.8 之前,HashMap 存在死循环的问题,这会导致 CPU 占用率达到 100%。
    • 从 JDK 1.8 开始,HashMap 在并发操作下可能会出现数据覆盖的情况,但仍然不是线程安全的。
  • ConcurrentHashMap 是线程安全的,它通过细粒度的锁机制来保证并发安全。在多线程环境下,不同的线程可以同时对不同的部分进行修改而不会影响到其他线程。
    • 它是 HashMap 的多线程安全版本,采用分段锁的机制,确保在高并发环境下能保持较好的性能和线程安全。

2. 性能差异

由于 ConcurrentHashMap 为了保证线程安全,通常会有一些性能上的开销。

  • HashMap 不进行任何同步操作,因此在单线程或少量线程并发的情况下,性能相对较高。
  • ConcurrentHashMap 由于实现了线程安全机制,通常会在性能上略低于 HashMap,尤其是在线程冲突较少的情况下,其性能差异更为明显。

3. 迭代时的行为差异

当在迭代 Map 时,如果对其进行修改(比如删除元素),HashMapConcurrentHashMap 的行为也有所不同:

  • HashMap 在迭代过程中进行删除或修改元素时,会抛出 ConcurrentModificationException,即它不允许在迭代过程中修改元素。
  • ConcurrentHashMap 支持在迭代时进行删除等操作,它是弱一致性的(weakly consistent),即可以在迭代过程中修改元素,而不会抛出异常。

4. 对 null 值的处理差异

  • HashMap 允许 keyvaluenull。你可以在 HashMap 中插入 null 键或值。
  • ConcurrentHashMap 不允许 keyvaluenull。这是因为,如果允许 null,就会引发二义性问题。例如,如果一个 key 被映射为 null,我们就无法区分该 key 是否存在,还是该 key 映射的值本身就是 null

5. 锁机制

ConcurrentHashMap 的线程安全是通过分段锁(Segment Locks)来实现的。具体来说,ConcurrentHashMap 会将 Map 分为多个段,每个段有自己的锁,这样线程可以在不同的段上进行并发操作,不会发生阻塞。相比之下,HashMap 不进行任何的锁机制,因此不支持并发修改。

6. 应用场景

  • HashMap 适合于单线程环境或并发较低的场景,因为它的性能较高且不需要同步。
  • ConcurrentHashMap 适合于多线程并发较高的场景,尤其是需要保证线程安全但又希望尽量避免阻塞的情况。

关于 HashMap 的死链问题和性能问题

在 JDK 1.8 之前,HashMap 使用头插法来处理哈希冲突,在高并发情况下,当满足以下三个条件时,可能会导致死链问题,进而导致 CPU 占用率达到 100%:

  1. 存在并发访问操作;
  2. 容器需要进行扩容操作;
  3. 使用了头插法解决哈希冲突。

当这三个条件同时满足时,可能会引发死链问题。为了避免这个问题,JDK 1.8 之后的 HashMap 引入了尾插法来解决哈希冲突,从而避免了死链问题。

如果你遇到 CPU 占用率过高的问题,并且确认使用的 JDK 版本是 JDK 1.8 及以上,通常这不再是由于 HashMap 本身的死链问题引起的。在这种情况下,应该检查你的代码和环境,查看是否有其他因素导致了性能瓶颈。可以通过 Linux 下的工具来进一步诊断 CPU 使用情况,并查看是否存在内存泄漏或其他并发问题。


代码示例:HashMapConcurrentHashMap 的基本使用

import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;

public class MapExample {
    public static void main(String[] args) {
        // 使用 HashMap
        HashMap<String, String> hashMap = new HashMap<>();
        hashMap.put("key1", "value1");
        hashMap.put("key2", "value2");
        
        System.out.println("HashMap:");
        for (String key : hashMap.keySet()) {
            System.out.println(key + " = " + hashMap.get(key));
        }
        
        // 使用 ConcurrentHashMap
        ConcurrentHashMap<String, String> concurrentHashMap = new ConcurrentHashMap<>();
        concurrentHashMap.put("keyA", "valueA");
        concurrentHashMap.put("keyB", "valueB");
        
        System.out.println("\nConcurrentHashMap:");
        for (String key : concurrentHashMap.keySet()) {
            System.out.println(key + " = " + concurrentHashMap.get(key));
        }
    }
}

总结

HashMapConcurrentHashMap 各有其优缺点,适用于不同的应用场景。理解它们的区别可以帮助我们在多线程编程中做出合适的选择。对于多线程并发较高的场景,推荐使用 ConcurrentHashMap,而对于简单的单线程或低并发环境,HashMap 性能更优。