【JavaEE】ConcurrentHashMap与Hashtable有什么区别?

文章对比了Hashtable和ConcurrentHashMap在多线程环境下的使用,重点分析了ConcurrentHashMap的锁分段技术和JDK1.8的优化,包括加锁粒度的细化、CAS机制的利用以及扩容策略的改进,强调了ConcurrentHashMap在性能和线程安全方面的优势。
摘要由CSDN通过智能技术生成

  • 博主简介:努力的打工人一枚
  • 博主主页:@xyk:
  • 所属专栏: JavaEE初阶

Hashtable、ConcurrentHashMap是使用频率较高的数据结构,它们都是以key-value的形式来存储数据,且都实现了Map接口,日常开发中很多人对其二者之间的区别并没有十分清晰的概念。 


目录

一、多线程环境使用哈希表

1.1什么是Hashtable?

1.2什么是ConcurrentHashMap?

二、ConcurrentHashMap的改进

三、ConcurrentHashMap和Hashtable的区别

3.1 加锁粒度的不同

3.2更充分的利用了CAS机制

3.3优化了扩容策略

四、相关面试题

4.1 ConcurrentHashMap的读是否要加锁,为什么?

4.2 介绍下 ConcurrentHashMap的锁分段技术?

4.3 ConcurrentHashMap在jdk1.8做了哪些优化?

4.4 Hashtable和HashMap、ConcurrentHashMap 之间的区别?


一、多线程环境使用哈希表

HashMap本身就不是线程安全的,那么多线程环境下使用哈希表可以使用:

  1. Hashtable
  2. ConcurrentHashMap

1.1什么是Hashtable?

本篇不再详细介绍,想要了解详细请点击:Hashtable是什么?它和Hashmap有什么区别?_xyk:的博客-CSDN博客

只是简单的把关键方法加上了synchronized关键字

 

 加到方法上,相当于是针对this加锁了

  1. 如果多线程访问同一个Hashtable就会直接造成锁冲突
  2. size属性也是通过synchronized来控制同步的,速度也是很慢的
  3. 一旦触发扩容,就由该线程完成整个扩容过程,这个过程会涉及到大量的元素拷贝,效率会非常低!!

1.2什么是ConcurrentHashMap?

 

ConcurrentHashMap底层是基于数组+链表,jdk1.7中的数据结构采用分段式设计,segment数组 + HashEntry数组 + 链表实现,hash冲突采用拉链法处理。

而在jdk1.8中,借鉴了jdk1.8中HashMap的设计思想,采用数组 + 链表 + 红黑树的数据结构,并且有原来的分段式锁换成了CAS + Synchronized锁,使用的是尾插法,其它的地方并没有改变。

二、ConcurrentHashMap的改进

相比于 Hashtable 做出了一系列的改进和优化. 以 Java1.8 为例;

  1. 读操作没有加锁(但是使用了 volatile 保证从内存读取结果), 只对写操作进行加锁. 加锁的方式仍然是是用 synchronized, 但是不是锁整个对象, 而是 "锁桶" (用每个链表的头结点作为锁对象), 大大降低了锁冲突的概率
  2. 充分利用 CAS 特性,比如 size 属性通过 CAS 来更新,避免出现重量级锁的情况.
  3. 优化了扩容方式:化整为零
  4. ConcurrentHashMap不允许key或value为null值

三、ConcurrentHashMap和Hashtable的区别

3.1 加锁粒度的不同

Hashtable是针对整个哈希表加锁,任何的增删改查操作,都会触发加锁,也就可能会有锁竞争!!实际上仔细思考,其实没必要把锁加的这么勤快

插入元素:

根据key计算hash值 ->数组下标,把这个新的元素给挂到对应的下标的链表上,那么如果是俩个线程,插入俩个元素,虽然俩个操作没有线程安全问题,但是由于synchronized是加到this上,仍然会针对同一个对象产生锁竞争,产生阻塞等待!!!

 

那么如果是ConcurrentHashMap不是只有一把锁了,每个链表的头节点都作为一把锁~~

每次进行操作,都是针对对应链表的锁进行加锁,那么操作不同的链表就是针对不同的锁加锁,不会有锁冲突;

导致大部分加锁操作实际上没有所冲突的!!此时加锁操作的开销就微乎其微了~~~

上述内容是ConcurrentHashMap和Hashtable之间最大,最关键的,最核心的区别

伪码:

void put(String key,String value){
//先找到对应的链表的头节点
 int index = hashCode(key); 
 Node head = getHead(index);
 synchronized(head){
   执行链表进行插入节点操作
 }
}

3.2更充分的利用了CAS机制

更充分的利用了CAS机制——无锁编程;有的操作,比如获取/更新元素个数,就可以直接使用CAS完成,不必加锁了。CAS也能保证线程安全,往往比锁更高效。

3.3优化了扩容策略

对于Hashtable来说,如果元素太多了,就会涉及扩容;扩容需要重新申请内存空间,搬运元素(把元素从旧的哈希表上删掉,插入到新的哈希表上)如果本身元素非常多,上亿个,全部搬运一次,成本就很高,就会导致这一次put操作效率很低,非常卡顿!!

那么ConcurrentHashMap策略,化整为零;并不会试图一次性的就把所有元素都搬运过去,而是每次就搬运一部分;当put触发扩容,此时就会直接创建更大的内存空间,但是并不会直接把所有元素都搬运过去,而是只搬运一小部分,速度还是比较快的;

此时,相当于存在俩份hash表了,如果要插入元素,直接往新表拆入;删除元素,删除旧表,或元素在哪个表就删除哪里;查找,新表旧表都查找!!并且每次操作过程中,都搬运一小部分元素过去~~~

四、相关面试题

4.1 ConcurrentHashMap的读是否要加锁,为什么?

读操作没有加锁. 目的是为了进一步降低锁冲突的概率. 为了保证读到刚修改的数据, 搭配了
volatile 关键字

4.2 介绍下 ConcurrentHashMap的锁分段技术?

这个是 Java1.7 中采取的技术. Java1.8 中已经不再使用了. 简单的说就是把若干个哈希桶分成一个"段" (Segment), 针对每个段分别加锁.
目的也是为了降低锁竞争的概率. 当两个线程访问的数据恰好在同一个段上的时候, 才触发锁竞争

4.3 ConcurrentHashMap在jdk1.8做了哪些优化?

取消了分段锁, 直接给每个哈希桶(每个链表)分配了一个锁(就是以每个链表的头结点对象作为锁对象).
将原来 数组 + 链表 的实现方式改进成 数组 + 链表 / 红黑树 的方式. 当链表较长的时候(大于等于8 个元素)就转换成红黑树

4.4 Hashtable和HashMap、ConcurrentHashMap 之间的区别?

  • HashMap: 线程不安全. key 允许为 null
  • Hashtable: 线程安全. 使用 synchronized 锁 Hashtable 对象, 效率较低. key 不允许为 null
  • ConcurrentHashMap: 线程安全,使用 synchronized 锁每个链表头结点,锁冲突概率低, 充分利用CAS 机制,优化了扩容方式,key 不允许为 null
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值