线程安全的集合

  • 1.JDK1.8底层数据结构与1.8之前的区别
  • 2.ConcurrentHashMap中putVal的流程
  • 3.为什么是tabAt(tab, i = (n - 1) & hash)),而不是table[index]?


1.JDK1.8底层数据结构与1.8之前的区别

1.8之前
HashMap底层是数组加链表的形式
数组的默认长度为16,加载因子为0.75,也就是160.75=12(阈值)
当计算出元素的位置在数组中冲突时,那么会以链表的形式存储新的元素,新的元素插在链表的头部,然后将链表下移,也就是将数组中的值赋值给新来的元素,当数组中12个位置被占据时(也就是达到了阈值),同时新插入的元素的插入位置不为空,就会进行扩容 2倍扩容,扩容完之后重新计算所有元素的hash值,(会产生死锁)
JDK8
HashMap底层是数组加链表加红黑树
数组的默认长度为16,加载因子为0.75,也就是16
0.75=12(阈值)
当计算出元素在数组中的位置相同时,则生成链表,并将新的元素插入到尾部,假如链表上元素超过了8个,那么链表将被改为红黑树,同时也提高了增删查效率
当数组元素个数达到了阈值,那么此时不需要判断新的元素的位置是否为空,数组都会扩容,2倍扩容,扩容完之后,之前的元素位置不会发生改变,也就不会产生死锁

2.ConcurrentHashMap中putVal的流程

1)判断存储的key/value是否为null,如果为null,则抛出异常,否则就进入2)
2)计算key的hash值,随后进入无限循环,确保成功插入数据,若table表为空或者长度为0,初始化table表示,否则,进入步骤3)
3)根据key的hash值取出table表中的节点元素,如果取出的节点为null,则使用CAS将key,value,hash值封装为一个node放在这个位置,否则,进入4)
4)如果当前节点的hash值为MOVED,则对桶中的节点进行转移,否则,进入步骤5)
5)对桶中的第一个节点进行加锁,对桶进行遍历,桶中的节点hash值与key值和给定的
hash值和key值相等,考虑新值覆盖旧值;如果遍历完没有找到hash值和key值相等的节点, 直接新生成一个节点并赋值为之前最后一个节点的下一个节点
6)红黑树进行红黑树插入一个新节点
7)若binCount值达到红黑树转化的阀值,则将桶中的结构转为红黑树进行存储

3.为什么是tabAt(tab, i = (n - 1) & hash)),而不是table[index]?

table[index]中没有任何其他元素,即此元素没有发生碰撞,这种情况直接CAS存储
hash计算的值一般都会很大,那么取余操作分部的话 基本都会落到最后一个桶里
tabAt(tab, i = (n - 1) & hash))
取余的效率没有位操作快,一般认为,Java的%、/操作比&慢10倍左右,因此采用&运算而不是h % length会提高性能。
因为HashMap中的容量都是2的幂次,这样的话2的幂-1都是11111结尾的(16->10000,15->01111),当容量一定是2^n时,tab[i = (2^n - 1) & hash] == tab [i=(hash%2^n)]