HashMap和ConCurrentHashMap

一、 HashMap

        HashMap是以key-value进行存储数据,由1.8开始进行分割,1.7时候的hashmap组成是数组加链表,而1.8之后是数组+链表/红黑树。 1.7和1.8的区别:

        1. 1.7中底层是数组+链表,1.8中底层是数组+链表+红⿊树,加红⿊树的⽬的是提⾼HashMap插⼊和查询整体效率。
        2. 1.7中链表插⼊使⽤的是头插法,A-B-C的顺序扩容之后可能变成C-B-A,1.8中链表插⼊使⽤的是尾插法,因为1.8中插⼊key和value时需要判断链表元素个数,所以需要遍历链表统计链表元素个数,所以正好就直接使⽤尾插法。
        3. 1.7中哈希算法⽐较复杂,存在各种右移与异或运算,1.8中进⾏了简化,因为复杂的哈希算法的⽬的就是提⾼散列性,来提供HashMap的整体效率,⽽1.8中新增了红⿊树,所以可以适当的简化哈希算法,节省CPU资源。

 HashMap1.7死循还

        HashMap1.7使用头插法会产生死循环的问题,这是因为两个线程同时进来的时候发现达到扩容的要求了打算对数组进行扩容,假设该数组位置存在是A-B-C,每一个都读取了一个数组位置存在的节点A,并记录了下一个节点B。此时,其中一个线程时间到了停止运行,另一个线程进行扩容然后讲路径边B-A,此时该线程结束之前停止线程开始运行,原本B之后是C,但是现在由于另一个线程的操作让B后面是A,自己记录里A下一个节点是B,做循环时会进入死循环。

其余问题

        并发赋值被覆盖: 在 createEntry 方法中, 新添加的元素直接放在头部,使元素之后可以被更快访问,但如果两个线程同时执行到此处,会导致其中一个线程的赋值被覆盖。

HashMap的put过程

1.7时候

        1.先判断是不是空,如果是空对数组进行初始化,默认16(有参数则大于参数的2的次幂),

        2.判断key是不是null,如果是null则遍历索引为0处的链表,找到为null的元素,找不到头插法插入,找到覆盖值。

        3.非空对hashcode进行扰动处理得到hash,之后理由hash&n-1得到数组索引。

        4.判断对于索引是否包含key,不包含则判断是否需要扩容,不需要则头插法插入元素。

 1.8时候

        1.先进性hash扰动,算hash值,通过hash%n-1计算索引位置

        2.判断对应位置是不是null,如果是则初始化链表,如果不是则判断第一个元素是不是与key相同,相同就覆盖。

        3.判断节点数据是不是红黑树,如果是红黑树就插入,如果不是红黑树则对链表进行遍历并统计链表个数,如果发现链表处有和key相等的值则覆盖返回,否则尾插法插入数据,并判断是否大于8,如果大于就进行红黑树转换(判断数组长度是否大于64,大于边红黑树,小于进行扩容)。

        4.插入数据成功判断元素个数是否满足扩容要求(0.75*n)

二、ConcurrentHashMap

        concurrentHashMap是线程安全的

1.7和1.8的不同

  1. 锁的粒度不同,JDK1.7版本锁的粒度是基于Segment的,而JDK1.8锁的粒度就是HashEntry(首节点)
  2. JDK1.8使用红黑树来优化链表,基于长度很长的链表的遍历是一个很漫长的过程,而红黑树的遍历效率是很快的,代替一定阈值的链表,这样形成一个最佳拍档
  3. JDK1.8为什么使用内置锁synchronized来代替重入锁ReentrantLock。

1.8使用    synchronized原因

  1. 因为粒度降低了,在相对而言的低粒度加锁方式,synchronized并不比ReentrantLock差,在粗粒度加锁中ReentrantLock可能通过Condition来控制各个低粒度的边界,更加的灵活,而在低粒度中,Condition的优势就没有了
  2.  JVM的开发团队从来都没有放弃synchronized,而且基于JVM的synchronized优化空间更大,使用内嵌的关键字比使用API更加自然
  3.  在大量的数据操作下,对于JVM的内存压力,基于API的ReentrantLock会开销更多的内存,虽然不是瓶颈,但是也是一个选择依据

Put操作

1.7

  1. 根据散列码hash( 调用segmentFor(hash) )找到对应的 Segment,如果该Segment还没有初始化,通过CAS进行初始化;
  2. 在这个 Segment 中执行具体的真正的 put 操作(segment.put( key, hash, value, false) )
  3. 加锁ReentranLock。对该Segment 对象而不是整个concurrentHashMap加锁
  4. 得到该散列码对应的 table 数组的下标值index,并找到散列码对应的具体的那个链表
  5. 遍历链表,若键 / 值对已存在,覆盖新值返回旧值; 键 / 值对不存在 ,创建新节点,并添加到链表的头部 ,返回 null
  6. 解锁该Segment

1.8

根据hash进行找到数组下标,判断是不是null,如果是空则CAS插入元素

如果正在扩容则帮助扩容

如果不是空则利用synchronized锁住第一个元素,以hashmap1.8的方法进行插入元素

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值