jdk8中的ConcurrentHashMap究竟为啥高效?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了jdk8中的ConcurrentHashMap究竟为啥高效?相关的知识,希望对你有一定的参考价值。
参考技术A 从源码来窥其一斑!我们都知道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其他的方法的源码分析,后期会补上的,更多的技术分享,敬请关注!
JDK1.8中的HashMap.HashTable, ConcurrentHashMap有什么区别?
JDK1.8中的HashMap,HashTable,ConcurrentHashMap有什么区别?
答:HashMap是线程不安全的,底层采用数组+链表+红黑树的结构
HashTable是线程安全的,因为使用了Synchronized锁住了整个table,底层采用了数组+链表
ConcurrentHashMap是线程安全的,采用了CAS+同步锁Synchronized对链表头节点进行锁定,底层使用数组+链表+红黑树
HashMap的key和value可以是null,其他两个不行。
以上是关于jdk8中的ConcurrentHashMap究竟为啥高效?的主要内容,如果未能解决你的问题,请参考以下文章
ConcurrentHashMap的computeIfAbsent方法在jdk8的bug
原来,JDK8的ConcurrentHashMap也会造成CPU 100%