03.07 jdk8中的ConcurrentHashMap究竟为什么高效?

此生唯一


从源码来窥其一斑!

我们都知道hashMap不是线程安全的,因为在扩容方法中很容易出现死循环,hashTable使用锁的方式比较简单暴力,几乎在所有操作方法上都加了synchronized锁,导致总体性能很差,concurrentHashmap凭借线程安全且性能优异一直都是高并发中的首选key-value型数据结构;

concurrentHashmap的高性能有以下原因:

一,分段锁:jdk8中对concurrentHashmap进行了改进,抛弃了jdk7中新建segment作为分段锁的过程,jdk8中虽沿用了这种分段锁的思想,却直接使用数组中的数据作为分段锁保证concurrentHashmap在上锁的时候只针对数组下标下的数据进行上锁(比如如果数组长度为256,那么每次put平均只有1/256的数据被锁),而大多数其他的数据还是能进行正常的增删改操作,无需阻塞等待,这无疑极大的降低了锁的粒度,提升了性能。


二,红黑树 :jdk8中引入了红黑树结构,在单个数组下标内的数据达到8以后,会自动转换为红黑树进行存储,使用大O表示法表示效率的话,红黑树的查找效率为O(log(n)),而链表的效率为O(n),当数据量越来越大的时候,红黑树的效率明显好于链表,所以concurrentHashmap性能得到很大提升;

现在我们主要从put方法中的主要方法来分析性能的提升:

spread(key.hashCode());//作用是再次哈希,减少冲突 ,源码如下


其中涉及到的位运算有

>>> 16:无符号右移16位,空位以0补齐 。

^:异或运算符-->相同为0,不同为1; &:与运算符-->全1得1,否则0;

(h ^ (h >>> 16)) & HASH_BITS; 所以这句代码的意思就是不仅消除高16位的影响,同时获得正整数的hash值

再来看后面的方法, 如上图:

1,就是判断当这个hash表还是空的时候,调用initTable进行初始化; 2,使用(n - 1) & hash)计算数组下标,如果数据指定下标处为null,则直接插入,注:cas是java8中的concurrentHashmap引入的线程安全判断,CAS算法做为乐观锁;

3,(fh = f.hash) == MOVED,走到此处说明下标内有node,且该node的值为-1(MODED=-1),搜索全类发现MODED是在调用有参构造器ForwardingNode中默认写入的,而这个调用处刚好在transfer方法中,所以我们推断,扩容的时候先将数组下标内的node.hash置为-1! 同时在3这一步中调用helpTransfer(tab, f)参与扩容,并把数据写入;


4,走到这说明node不是空的,也没在扩容,那么锁住该下标下的node,并把新value插入链表中; 5,如果锁住的这个node能实例化为TreeBin,则代表已经转化为红黑树进行存储,将数据插入红黑树中; 6,判断在4,5中计算得到的数组下标内所有节点总数,如果满足转化为红黑树的条件(节点数大于8),则自动转化为红黑树进行存储!

总的来说,concurrentHashmap之所以性能高就是因为使用了分段锁和红黑树!

至于conrrentHashmap其他的方法的源码分析,后期会补上的,更多的技术分享,敬请关注!


分享到:


相關文章: