HashTable、HashMap、ConcurrentHashMap 都是存储键值对的容器,其区别如下:
-
HashTable是线程安全的,HashMap 不是线程安全的。在并发环境下,HashTable 性能表现比 HashMap 差。 HashTable 不允许 key 或 value 为 NULL,而 HashMap 允许 key 或 value 为 NULL。 HashTable 进行 resize 时,会锁住整个 HashTable 表,导致所有操作被阻塞;而 HashMap 初始大小固定,resize 导致效率低下,但是 JDK1.8 之后 HashMap 进行 resize 时,并不会锁整个 HashMap,而是只锁住相关 Node 链表,这一点在多线程环境中大大提高了效率。
-
HashMap 与 ConcurrentHashMap HashMap 是非线程安全的,ConcurrentHashMap 是线程安全且并发性高的。
ConcurrentHashMap 采用分段锁技术,将哈希表分为多个 Segment(段),每个 Segment 采用独立锁实现并发控制,不同线程访问不同 Segment 时,不会发生锁竞争,从而提高了并发访问效率。而 HashMap 在并发访问时需要采用锁机制进行同步,锁竞争导致效率降低。
总结:如果需要在多线程环境下使用容器,建议使用 ConcurrentHashMap;如果对容器线程安全没有要求,且在单线程环境下访问比较频繁,推荐使用 HashMap。而因为 HashTable 性能比较低,建议尽量避免使用 HashTable。
对于线程安全的hashTable来说, 只是简单的把put和get方法加上了synchronized关键字, 也就是说在一个线程进行put或者get的时候, 其他线程需要阻塞等待, size属性也是被synchronized修饰的, 访问起来比较慢, 一旦触发扩容, 对于hashTable来说就意味着大量的内存拷贝, 此时当元素多了之后 ,拷贝效率就会非常的低下.
对于ConcurrentHasMap, 相比于 Hashtable 做出了一系列的改进和优化. 以 Java1.8 为例 读操作没有加锁(但是使用了 volatile 保证从内存读取结果), 只对写操作进行加锁. 加锁的方式仍然 是是用 synchronized, 但是不是锁整个对象, 而是 "锁桶" (用每个链表的头结点作为锁对象), 大大降 低了锁冲突的概率. 充分利用 CAS 特性. 比如 size 属性通过 CAS 来更新. 避免出现重量级锁的情况. 优化了扩容方式: 化整为零 发现需要扩容的线程, 只需要创建一个新的数组, 同时只搬几个元素过去.