java-concurrentHashMap

put方法执行逻辑

1. 初始化方法initTable 这个方法在自旋

  在第一次put的时候table尚未初始化,会调用初始化table的方法,初始化方法支持并发,但是只会有一个线程去执行table数组的初始化,创建的线程会把sizeCtl属性设置为-1代表正在初始化,其他线程检查sizeCtl属性是发现是-1就让出cpu执行权, 也就是创建一个node[] 赋值给map的table属性

2. 执行完初始化table方法之后会自旋,继续put值 这时会根据key定位到table对应的桶位,如果桶位为null 就使用cas放入node 如果cas放入成功退出自旋 如果失败说明冲突了 继续自旋

3. 判断是否是在扩容 如果在扩容中 当前put线程帮助扩容 否则进行进行下次自旋

4. 根据线程hash定位到的桶位拿到桶位头结点,以这个对象为锁,如果桶位是链表就对比并替换,如果是红黑树就替换或插入

5. 然后判断是否需要扩容table,如果到达了扩容阈值就扩容

6.扩容时如果是第一个进来扩容是线程会创建一个新的表长度是旧table的1倍 然后线程进入一个自旋

7. 如果迁移已经完成就执行退出逻辑,如果未完成查看桶位是否已经分配完 分配完直接退出 未分配完分配新的迁移区间

8. 然后线程执行迁移逻辑如果迁移完再去获取新的迁移区间等所有的迁移完才能退出

9. 如果迁移的桶位是null直接赋值fwd代表已经迁移完成,如果桶位不为null就使用当前桶位的头结点作为锁,迁移的桶位是链表就,把旧链表分高低链表,分别放入fwd节点持有的newTable的原始 桶位和原始桶位+扩容值的桶位

10. 如果迁移的桶位是红黑树,因为concurrentHashmap持有的treenode持有红黑树和双向链表两个数据结构,迁移的时候使用的双向链表,把双向链表拆分成高低链,拆完如果链表小于等于6就把treenode转为普通的node,如果大于6就还封装为红黑树,存储新的桶位 线程进入下个桶位处理迁移

这里有的细节是:如果红黑树只分出一个链来,并且链的长度大于6,说明这个旧的红黑树全是低位链或者全是高位链,就把这个红黑树直接付给新的桶位即可

 

get方法执行逻辑

根据key的hash定位到table的桶位,对比一样就返回val,不一样的话如果是链表就遍历对比,如果桶位存储的是fwd节点或者红黑树节点,就进入fwd去查询,fwd中存储的也是table逻辑跟外层一样先找到key转为hash值后求出对应桶位,如果是链表就遍历对比,如果是fwd就再进入 如果是红黑树就再红黑树里找 不存再就返回null

 

 

remove方法执行逻辑

没什么可说的,调用的concurrentHashMap 的 replaceNode 方法,先根据key定位到slot,如果正在扩容就帮助扩容,如果slot上的是链表就遍历比对赋值null,如果是红黑树,就搜索红黑树然后删除

 

 

问题汇总:

1. 并发map如何保证put操作线程安全的呢

通过key定位到对应的slot之后,如果slot为null 线程使用cas方式向slot中写入数据成功返回,如果失败说明有其他线程竞争到这个位置了会自旋重试

发现slot的桶位有节点之后会sync锁住头节点以保证串型处理

2. 并发map的寻址算法

与普通map类似

3. 并发map使用longadder来统计元素数量为什么不用atomiclong?

atomiclong使用的cas操作 当多个线程并发执行的时候 通过系统底层的cmpxchg执行实现互斥性,线程先比较期望值一致才进行负值操作 当一个线程成功负值之后其他线程会全部失败

longadder内部有一个long类型base值与一个cell[] 如果未发生并发情况使用cas更新base值 如果cas失败会创建cell 通过线程的hash值位于运算定位到cell 进行cas写入cell中的long中

4. 触发扩容的线程做了什么事情?

  1. 会计算出一个sizectl 高16位是扩容统一标识戳 低16位为扩容线程数

  2. 创建一个新的table是旧table的2倍

  3. 从高位开始迁移直到下标为0的桶位

  4. 被迁移完毕的桶位会被设置为fwd节点 指向新构建的table

5. 在扩容期间put操作如何处理?

  1. 如果未迁移执行普通的加锁put操作

  2. 如果定位到的slot是fwd节点会协助迁移数据根据全局字段transferindex去迁移一段区间的桶位

6. 最后一个扩容线程会做什么特殊收尾操作?

  1. 检查一边老表全都是fwd节点 如果不是说明遗漏了桶位会进行迁移

  2. 将新表引用更新 计算出下一次的扩容阈值

7. 当treebin有读线程,这时有写线程如何处理?

  写线程会更新state值表示等待可写状态并park挂起 读线程执行完读操作之后会检查state值判断是否有读线程挂起 如果有会唤醒

8. treebin有写线程,这时读线程来了如何处理?

  treebin有两个数据结构一个红黑树一个链表,读请求会检查state发现正进行写操作,会去链表读数据

posted @ 2021-03-11 20:36  rudynan  阅读(141)  评论(0编辑  收藏  举报