集合之HashMap 复盘:扩容机制,ConcurentHashMap jdk1.7 与 1.8的对比

这篇博客深入探讨了HashMap在Java 7中的线程不安全原因和扩容死锁问题,同时对比了Java 8中的优化,包括使用四个指针的设计。文章还详细分析了ConcurrentHashMap如何实现线程安全,并阐述了Java 7与8中ConcurrentHashMap的主要区别,包括不同版本的实现原理和操作如put、get及resize等。
摘要由CSDN通过智能技术生成

集合之HashMap 复盘:

 

阅读源码:

1.默认数组是16,但是可以自己指定。 预先指定hashmap的size为2的整数次幂次方。就算不指定的话,也会以大于且最接近指定值大小的2次幂来初始化的,
eg:
11-->16
17-->32

 

2.HashMap的扩容机制:
触发条件: hashmap中的元素个数超过数组大小*loadFactor加载因子
eg:16*0.75=12 ---->重新扩大一倍为32
然后 重新计算每个元素在数组中的位置 ,而这是一个非常消耗性能的操作

 

3.jdk7中:HashMap线程不安全的原因

  1.高并发的情况下,HashMap的resize扩容操作会导致线程不安全(死锁,cpu使用率达到100%)
  2.还会有数据丢失的情况(扩容的原理是再新申请一个比旧数组*2的新数组,高并发多线程环境下可能出现jvm内存溢出,触发大量GC)

4,扩容死锁的原因:(Java7)

简单的说就是扩容后,链表可能会成环,(两个指针e,next()) Hashmap多线程会导致HashMap的Entry链表形成环形数据结构,一旦形成环形数据结构,Entry的next节点永远不为空,就会产生死循环获取Entry。
 

java8的优化:用了四个指针指向

 

5.concurentHashMap是怎么做到线程安全?

阅读源码可知,
concurentHashMap不允许key和value 为空。
 
HashMap对象的key、value值均可为null。
HahTable对象的key、value值均不可为null。且两者的的key值均不能重复,若添加key相同的键值对,后面的value会自动覆盖前面的value,但不会报错。
 

 

6.Java7 和Java 8中,concurenthashmap的区别

 
 

1.7中的原理和实现

   1.7中采用的是数组+单向链表的结构, ConcurrentHashMap将数据分别放到多个Segment中,默认16个,每一个Segment中又包含了多个HashEntry列表数组
需要注意的是 Segment 是一种可重入锁(继承ReentrantLock)
 
 
 

 

1.7的扩容操作

Segment 不扩容(默认l6个 不变),扩容下面的table数组(初始化时为2),每次都是默认将数组翻倍

1.8中的原理和实现

1、原理

 
   1.8中是采用数组+链表+红黑树的结构, 对于链表个数小于等于默认值8的链表使用单向链接,而个数超对默认值的时候将链表转换为红黑树,查询时间复杂度优化到了O(logN)【二分查找】,O(logN)即二叉树的深度
放弃了segment分段式锁链表,并发控制使用SynchronizedCAS来操作
加锁的位置,在每个链表的头结点或者是红黑树的根节点
 

2、put()

1.8:由于移除了Segment,类似HashMap,可以直接定位到桶,拿到first节点后进行判断,1、为空则CAS插入;2、为-1则说明在扩容,则跟着一起扩容;3、else则加锁put(类似1.7)
 

3、get()

基本类似,由于value声明为volatile,保证了修改的可见性,因此不需要加锁。

4、resize()

1.8:支持并发扩容,HashMap扩容在1.8中由头插改为尾插(为了避免死循环问题),ConcurrentHashmap也是,迁移也是从尾部开始,扩容前在桶的头部放置一个hash值为-1的节点,这样别的线程访问时就能判断是否该桶已经被其他线程处理过了

 

 

 

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值