**HashMap与HashTable与ConcurrentHashMap**比较

HashMap与HashTable的区别

  • HashMap线程不安全,效率高

  • HashMap是Hashtable的轻量级实现,他们都完成了Map接口,主要区别在于HashMap允许空(null)键值(key),由于非线程安全,效率上可能高于Hashtable。

  • HashTable线程安全,效率低;

  • HashMap的初始容量为16,Hashtable初始容量为11,两者的填充因子默认都是0.75。

  • HashMap扩容时是当前容量翻倍即:capacity x 2,Hashtable扩容时是容量翻倍+1即:capacity x 2+1。

  • 底层原理比较

  • HashMap:

    • JDK 1.8的 改变

    • HashMap采用了数组+链表+红黑树的实现,当链表长度超过阈值(8)时,将链表转换为红黑树。(红黑树查询删除快新增慢)在性能上进一步得到提升。

    • JDK1.7在扩容时是直接头插法倒叙排一遍

      • 当 Hash 冲突严重时,在桶上形成的链表会变的越来越长,这样在查询时的效率就会越来越低;时间复杂度为 O(N)

    • jdk1.8 HashMap在扩容时保持了原来链表中的顺序

    • 底层是数组加连表实现

      • 数组:数组的特点是:寻址容易,插入和删除困难;

      • 链表:寻址困难,插入和删除容易。

      • 解决hash冲突

      • 开放定址法(线性探测再散列,二次探测再散列)

      • 再哈希法

      • 链地址法

      • 建立一个公共溢出区

*ConcurrentHashMap

  • jdk1.7中 也是由 Segment 数组、HashEntry 组成,和 HashMap 一样,仍然是数组加链表。

  • 由于 HashEntry 中的 value 属性是用 volatile 关键词修饰的,保证了内存可见性,所以每次获取时都是最新值。

    ConcurrentHashMap 的 get 方法是非常高效的,因为整个过程都不需要加锁

    • 缺点

    • 那就是查询遍历链表效率太低。

  • jdk1.8中 其中抛弃了原有的 Segment 分段锁,而采用了 CAS + synchronized 来保证并发安全性。

  • 也将 1.7 中存放数据的 HashEntry 改为 Node,但作用都是相同的。

    其中的 val next 都用了 volatile 修饰,保证了可见性

  • 改动之处:

    • 采用红黑树之后可以保证查询效率(O(logn)),甚至取消了 ReentrantLock 改为了 synchronized,这样可以看出在新版的 JDK 中对 synchronized 优化是很到位的。

三者之间的联系 ** HashMap**&&HashTable&&ConcurrentHashMap**

  • 在并发编程中使用HashMap可能导致程序死循环。而使用线程安全的HashTable效率又非常低下,基于以上两个原因,便有了ConcurrentHashMap的登场机会

  • 线程不安全的HashMap

    • 在多线程环境下,使用HashMap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap。HashMap在并发执行put操作时会引起死循环,是因为多线程会导致HashMap的Entry链表形成环形数据结构,一旦形成环形数据结构,Entry的next节点永远不为空,就会产生死循环获取Entry。

  • 效率低下的HashTable

    • HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。因为当一个线程访问HashTable的同步方法,其他线程也访问HashTable的同步方法时,会进入阻塞或轮询状态。如线程1使用put进行元素添加,线程2不但不能使用put方法添加元素,也不能使用get方法来获取元素,所以竞争越激烈效率越低。

  • ConcurrentHashMap的锁分段技术可有效提升并发访问率

    • HashTable容器在竞争激烈的并发环境下表现出效率低下的原因是所有访问HashTable的线程都必须竞争同一把锁,假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术。首先将数据分成一段一段地存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问.

  • *ConcurrentHashMap

    • jdk1.7中采用Segment + HashEntry的方式进行实现

    • Segment是一种可重入锁(ReentrantLock)(数组加链表实现)

    • jdk1.8中取消了Segment分段锁的数据结构,取而代之的是数组+链表(红黑树)的结构,因此在链表节点数量大于8时,会将链表转化为红黑树进行存储。这样一来,查询的时间复杂度就会由原先的O(n)变为O(logN)。下面是其基本结构:

    • 取而代之的是采用Node + CAS + Synchronized来保证并发安全进行实现,

上一篇:Hashtable与ConcurrentHashMap源码分析


下一篇:公开课--redis秒杀和公开锁----1