ConcurrentHashMap1.7源码详细分析 主要是put方法

此分析是根据视频中讲解截图,并做记录可能有些混乱,在看时手边能有源码 效果更好

分析前有一些基本的介绍

1、ConcurrentHashMap是segment类型的数组。

2、segment类型中有HashEntry类型的数组、loadfactor加载因子、threshold扩容阈值HashEntry 有hash、key、value、HashEntry next属性。

3、一个segment对象元素其实很类似于一个HashMap,Segment 类型继承了ReentrantLock 所以可以跟每个segment对象加锁。
4、ConcurrentHashMap中segment数组默认长度是16 加载因子是0.75 并发级别是16,根据逻辑代码segment数组长度和每个segment对象中的HashEntry数组的长度都是2的幂次方数。

5、初始化时会在segment数组中下标为0的地方放入一个s0的元素,以供后续计算使用。

分析put方法put(key,value);

Put时key不能为null 为null时抛出异常

根据key的hash值计算出segments数组对应的下标 j ,通过unsafe的方法取到数组中第j个位置的segment对象 并给s赋值 然后判断 s是否等于null

下面是要生成新segment对象的流程

s为null是 就会调用ensureSegment(j)生成一个新的segment对象并给s赋值

 ensureSegment方法详情,调用时会有并发问题 根据传入的key计算出下标u,取到原数组ss,例如两个线程,在执行到这个判断条件时(底层unsafe中的方法)如果为false 则说明seg对象存在 直接返回

若果没有则继续执行if判断体生成seg对象并返回

从segments数组中取到第0个位置的segment对象 并取出其中的属性值,为后续生成seg对象赋值做准备,如图还生成一个seg对象的中属性的HashEntry数组 tab

再一次判断是否生成了seg对象,没有的话则创建一个segment对象s 并将上述的属性赋值

通过while+unsafe cas的方式再次判断是否生成了seg对象,如果没有则通过unsafe的方法将 s对象赋值给seg,下图表示ss 数组中第u个位置的segment对象是否为null 如果为null则将s赋值给seg并将s保存到内存中 并跳出循环 如果此时有另外一个线程执行到中这里时就会false 然后再次循环 在while判断条件中拿到seg对象 并返回seg对象

上述过程中重复调用unsafe的方法 就是为了保证多线程情况下 在第u位置只有一个segment 对象

Segment对象生成之后要把对应的key和value放入到对象中去的流程

return s.put(key,hash,value,false)该方法是入口

具体的put方法体部分如下因为看着视频截图 所以全部的方法 分了好几块

方法的的大概流程就是放入元素前要先加锁

取到segment对象中的HashEntry数组table,根据传入的hash值结合table的 长度算出下标index  此时的index其实是table数组的下标。然后判读table中对应下标的链表元素first中遍历是和key 相同的节点 有则覆盖 无则添加  ,当然流程没这么简单,里面还设计到了多线程安全问题和扩容的问题。该流程有写类似于hashmap的流程。在遍历过程中如果没有的话则采取的也是头插法 创建新的HashEntry对象,并将取到的first放入到新的HashEntry对象的next 属性上

然后将新 生成的HashEntry对象放入到对应的下标的table数组中

详解put方法开始获取锁的过程

如果tryLock()获取到了锁就给 node赋值null,否则执行scanAndLockforPut方法,该方法返回的是一个HashEntry对象,同样赋值给node。

下面是scanAndLockforPut的详细流程,里面有好几个if-else。这里使用到了while(tryLock())和lock()(这个是阻塞的)。大概的流程是这样的

1 先获取头节点first,first赋值给e,retries赋值为-1

2 while循环遍历e这个链表,e为空时新建一个HashEntry对象node retries赋值0,如果e不 为空则判断e中的key和新加的key是否相等  相等的话 retries赋值为0,此时不相等的话则遍历e节点的下一个节点 e.next。

3 当retries都不小于0时循环时就会++retries,到达一定的数量时就会调用lock()获取锁并跳出循环。

4 retries还没有到达要跳出的时候还有循环并且当retries为偶数的时候还会重新取出链表的第一个节点赋值给f 并判断是否和first相等,如果不相等则有e=first=f retries=-1,接着while的循环遍历。这是因为在循环while时可能会有某个线程把要遍历的链表第一个节点给改变了。哎呀这是费劲,这么多的遍历

扩容的入口rehash(node) node是新节点

扩容扩的是segment数组中的每个HashEntry数组元素而不是segment数组本身

该rehash方法分为了四部分 我认为的,先获取到老的segment对象中的老HashEntry数组oldTable和数组长度oldcapacity,计算出新数组的长度newCapacity,计算出新的阈值,创建新的HashEntry数组newTable 长度为newCapacity,然后遍历根据oldcapacity遍历oldTable

1 取到第一个元素e,第一个元素不为null 根据元素中hash值计算在新数组中的下标idx,第一个元素的next属性为null时,直接将e放到新数组中下标为idx的位置上。

2 e的next属性不为null时,那就遍历所在的链表了,遍历链表中有两个for循环

  1. 第一个for循环是从e的next节点开始,其中lastRun=e,last开始为next=e.next。

前面已经计算了一个下标idx,赋值给lastIdx,遍历中根据每个元素的hash值计算新数组的下标k,将k!=lastIdx时 有lastIdx=k,lastRun=last,就这样一直循环 直到最后一个k!=lastIdx不符合的元素temp,将此元素temp以及后面的链表赋值到新数组中下标为lastIdx的上面,这个元素后面的元素计算下标的值应该是一样的,不然也不会那么赋值,hash值计算下标值可能有一定的 规律,这里就不赘述了,我也没有试过。

3 遍历从e开始一直到 链表中节点等于temp中间的节点,计算下标k,根据下标取出新数组中的元素n,创建新节点new HashEntry 并将n赋值到新节点的next属性上,新节点赋值给下标为k的newTable中作为第一个节点  这也是头插法吧和hashmap的一样。

4 将新添加的node 计算下标nodeIndex,找到新数组的中下标为nodeIndex的头节点其实也是链表元素,然后赋值给新节点node的next属性上,node节点在放入新数组中成为头节点,此时扩容完成新节点也插入了,将新的newTable赋值给对应的segment对象的table元素上。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值