关于java:HashMap和Hashtable以及ConcurrentHashMap的区别

4次阅读

共计 4791 个字符,预计需要花费 12 分钟才能阅读完成。

HashMap 和 Hashtable 的区别

何为 HashMap

HashMap是在 JDK1.2 中引入的 Map 的实现类。

HashMap是基于 哈希表 实现的,每一个元素是一个 key-value 对,其外部通过单链表解决抵触问题,容量有余(超过了阀值)时,同样会主动增长。

其次,HashMap 非线程平安 的,只是用于单线程环境下,多线程环境下能够采纳 concurrent 并发包下的concurrentHashMap

如果不了解线程平安,能够看看我这篇文章:Java 并发编程之多线程

HashMap 实现了 Serializable 接口,因而它 反对序列化 ,实现了Cloneable 接口,能被克隆。

HashMapkeyvalue都容许为 nullkeynull的键值对永远都放在以 table[0] 为头结点的链表中。

何为 Hashtable

Hashtable 同样 也是基于哈希表 实现的,同样每个元素是一个 key-value 对,其外部也是通过单链表解决抵触问题,容量有余(超过了阀值)时,同样会主动增长

Hashtable 也是 JDK1.0 引入的类,是线程平安的,能用于多线程环境中。

Hashtable 同样实现了 Serializable 接口,它反对序列化,实现了 Cloneable 接口,能被克隆。

也就是说,这两个货色大部分时雷同的。

Hashtable 与 HashMap 的不同

首先,从下面能够得出,线程平安是不同的。

  1. HashMap 线程不平安,HashTable 线程平安。
  2. 蕴含的 contains 办法不同,HashMap 是没有 contains 办法的。
  3. Hashmap 是容许 key 和 value 为 null 值的。
  4. 计算 hash 值形式不同。
  5. . 扩容形式不同。
  6. 解决 hash 抵触形式不同。
  7. HashMap 是继承自 AbstractMap 类,而 Hashtable 是继承自 Dictionary 类。
  8. 对外提供的接口不同,Hashtable 比 HashMap 多提供了 elements() 和 contains() 两个办法。

如果你须要具体具体的理解不同,能够返回浏览器获取具体区别与原理。

ConcurrentHashMap 作用

看看上面我箭头指的中央。

因为 HashMap 是线程不平安的,尽管 Hashtable 是线程平安的,可是他是一个被舍弃的类,既然淘汰了,那咱们就根本不必了。

那什么货色能够代替 Hashtable 成为 HashMap 的线程安全类呢?

concurrentHashMap能够用于并发环境,他是反对线程平安的。

线程不平安的 HashMap, 线程不平安的HashMap,促使了ConcurrentHashMap 在 JDK5 诞生。

HashTable容器在竞争强烈的并发环境下体现出效率低下的起因,是因为所有拜访 HashTable 的线程都必须竞争同一把锁。那如果容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程拜访容器里不同数据段的数据时,线程间就不会存在锁竞争,从而能够无效的进步并发拜访效率,这就是 ConcurrentHashMap 所应用的锁分段技术,首先将数据分成一段一段的存储,而后给每一段数据配一把锁,当一个线程占用锁拜访其中一个段数据的时候,其余段的数据也能被其余线程拜访。另外,ConcurrentHashMap能够做到读取数据不加锁,并且其外部的构造能够让其在进行写操作的时候可能将锁的粒度放弃地尽量地小,不必对整个 ConcurrentHashMap 加锁。

ConcurrentHashMap是由 Segment 数组构造和 HashEntry 数组构造组成。Segment是一种可重入锁 ReentrantLock,在ConcurrentHashMap 里表演锁的角色,HashEntry则用于存储键值对数据。一个 ConcurrentHashMap 里蕴含一个 Segment 数组,Segment的构造和 HashMap 相似,是一种数组和链表构造,一个 Segment 里蕴含一个 HashEntry 数组,每个 HashEntry 是一个链表构造的元素,每个 Segment 守护着一个 HashEntry 数组里的元素,当对 HashEntry 数组的数据进行批改时,必须首先取得它对应的 Segment 锁。

ConcurrentHashMap为了进步自身的并发能力,在外部采纳了一个叫做 Segment 的构造,一个 Segment 其实就是一个类 HashTable 的构造,Segment外部保护了一个链表数组。

具体理解还是看看其余文章吧,我只是提出有这个货色能够实现并发汇合。

ConcurrentHashMap 与其余类的区别

与 HashMap 的区别是什么?

ConcurrentHashMap 是 HashMap 的升级版,HashMap 是线程不平安的,而 ConcurrentHashMap 是线程平安。而其余性能和实现原理和 HashMap 相似。

与 Hashtable 的区别是什么?

Hashtable 也是线程平安的,但每次要锁住整个构造,并发性低。相比之下,ConcurrentHashMap 获取 size 时才锁整个对象。

Hashtable 对 get/put/remove 都应用了同步操作。ConcurrentHashMap 只对 put/remove 同步。

Hashtable 是疾速失败的,遍历时扭转构造会报错 ConcurrentModificationException。ConcurrentHashMap 是平安失败,容许并发检索和更新。

而后,在腾讯云社区,我还看到了一个区别,JDK8 的 ConcurrentHashMap 和 JDK7 的 ConcurrentHashMap 的区别。

JDK8 的 ConcurrentHashMap 和 JDK7 的 ConcurrentHashMap 有什么区别?

其余个性

ConcurrentHashMap 是如何保障并发平安的?

JDK7 中 ConcurrentHashMap 是通过 ReentrantLock+CAS+ 分段思维来保障的并发平安的,ConcurrentHashMap 的 put 办法会通过 CAS 的形式,把一个 Segment 对象存到 Segment 数组中,一个 Segment 外部存在一个 HashEntry 数组,相当于分段的 HashMap,Segment 继承了 ReentrantLock,每段 put 开始会加锁。

在 JDK7 的 ConcurrentHashMap 中,首先有一个 Segment 数组,存的是 Segment 对象,Segment 相当于一个小 HashMap,Segment 外部有一个 HashEntry 的数组,也有扩容的阈值,同时 Segment 继承了 ReentrantLock 类,同时在 Segment 中还提供了 put,get 等办法,比方 Segment 的 put 办法在一开始就会去加锁,加到锁之后才会把 key,value 存到 Segment 中去,而后开释锁。同时在 ConcurrentHashMap 的 put 办法中,会通过 CAS 的形式把一个 Segment 对象存到 Segment 数组的某个地位中。同时因为一个 Segment 外部存在一个 HashEntry 数组,所以和 HashMap 比照来看,相当于分段了,每段外面是一个小的 HashMap,每段专用一把锁,同时在 ConcurrentHashMap 的构造方法中是能够设置分段的数量的,叫做并发级别 concurrencyLevel.

JDK8 中 ConcurrentHashMap 是通过 synchronized+cas 来实现了。在 JDK8 中只有一个数组,就是 Node 数组,Node 就是 key,value,hashcode 封装进去的对象,和 HashMap 中的 Entry 一样,在 JDK8 中通过对 Node 数组的某个 index 地位的元素进行同步,达到该 index 地位的并发平安。同时外部也利用了 CAS 对数组的某个地位进行并发平安的赋值。

JDK8 中的 ConcurrentHashMap 为什么应用 synchronized 来进行加锁?

JDK8 中应用 synchronized 加锁时,是对链表头结点和红黑树根结点来加锁的,而 ConcurrentHashMap 会保障,数组中某个地位的元素肯定是链表的头结点或红黑树的根结点,所以 JDK8 中的 ConcurrentHashMap 在对某个桶进行并发安全控制时,只须要应用 synchronized 对以后那个地位的数组上的元素进行加锁即可,对于每个桶,只有获取到了第一个元素上的锁,能力操作这个桶,不论这个桶是一个链表还是红黑树。

想比于 JDK7 中应用 ReentrantLock 来加锁,因为 JDK7 中应用了分段锁,所以对于一个 ConcurrentHashMap 对象而言,分了几段就得有几个 ReentrantLock 对象,示意得有对应的几把锁。

而 JDK8 中应用 synchronized 关键字来加锁就会更节俭内存,并且 jdk 也曾经对 synchronized 的底层工作机制进行了优化,效率更好。

JDK7 中的 ConcurrentHashMap 是如何扩容的?

JDK7 中的 ConcurrentHashMap 和 JDK7 的 HashMap 的扩容是不太一样的,首先 JDK7 中也是反对多线程扩容的,起因是,JDK7 中的 ConcurrentHashMap 分段了,每一段叫做 Segment 对象,每个 Segment 对象相当于一个 HashMap,分段之后,对于 ConcurrentHashMap 而言,能同时反对多个线程进行操作,前提是这些操作的是不同的 Segment,而 ConcurrentHashMap 中的扩容是仅限于本 Segment,也就是对应的小型 HashMap 进行扩容,所以是能够多线程扩容的。

每个 Segment 外部的扩容逻辑和 HashMap 中一样。

JDK8 中的 ConcurrentHashMap 是如何扩容的?

首先,JDK8 中是反对多线程扩容的,JDK8 中的 ConcurrentHashMap 不再是分段,或者能够了解为每个桶为一段,在须要扩容时,首先会生成一个双倍大小的数组,生成完数组后,线程就会开始转移元素,在扩容的过程中,如果有其余线程在 put,那么这个 put 线程会帮忙去进行元素的转移,尽管叫转移,然而其实是基于原数组上的 Node 信息去生成一个新的 Node 的,也就是原数组上的 Node 不会隐没,因为在扩容的过程中,如果有其余线程在 get 也是能够的。

JDK8 中的 ConcurrentHashMap 是如何扩容的?

CounterCell 是 JDK8 中用来统计 ConcurrentHashMap 中所有元素个数的,在统计 ConcurentHashMap 时,不能间接对 ConcurrentHashMap 对象进行加锁而后再去统计,因为这样会影响 ConcurrentHashMap 的 put 等操作的效率,在 JDK8 的实现中应用了 CounterCell+baseCount 来辅助进行统计,baseCount 是 ConcurrentHashMap 中的一个属性,某个线程在调用 ConcurrentHashMap 对象的 put 操作时,会先通过 CAS 去批改 baseCount 的值,如果 CAS 批改胜利,就计数胜利,如果 CAS 批改失败,则会从 CounterCell 数组中随机选出一个 CounterCell 对象,而后利用 CAS 去批改 CounterCell 对象中的值,因为存在 CounterCell 数组,所以,当某个线程想要计数时,先尝试通过 CAS 去批改 baseCount 的值,如果没有批改胜利,则从 CounterCell 数组中随机取出来一个 CounterCell 对象进行 CAS 计数,这样在计数时进步了效率。

所以 ConcurrentHashMap 在统计元素个数时,就是 baseCount 加上所有 CountCeller 中的 value 值,所得的和就是所有的元素个数。

注:其余个性局部来自于腾讯云 + 社区

正文完
 0