扫码关注公众号
键值对的数量是如何保存到 baseCount 和 counterCells 中的呢?
addCount(),它会先对CHM中所有键值对计数,然后考虑是否扩容。现在我们来看看它是如何计数的。privatefinalvoidaddCount(longx,intcheck){CounterCell[]as;longb,s;//第一层ifif((as=counterCells)!=null||!U.compareAndSwapLong(this,BASECOUNT,b=baseCount,s=b+x)){CounterCella;longv;intm;booleanuncontended=true;//第二层ifif(as==null||(m=as.length-1)<0||(a=as[ThreadLocalRandom.getProbe()&m])==null||!(uncontended=U.compareAndSwapLong(a,CELLVALUE,v=a.value,v+x))){fullAddCount(x,uncontended);return;}if(check<=1)return;s=sumCount();}if(check>=0){...}}addCount()中有两层if,其中蕴含了非常巧妙的设计,第一层:counterCells不为空:跳转第二层if;counterCells为空:不着急创建counterCells,先用原子操作增加baseCount:成功:结束计数;失败:跳转第二层if。第二层if就是用来创建counterCells以及使用它来计数的:counterCells为空或counterCells[probe]为空:调用fullAddCount(x,true);counterCells[probe]已经创建:原子操作增加其value值:成功:结束计数;失败:调用fullAddCount(x,false)。
ConcurrentHashMap与HashMap等的区别
1.HashMap我们知道HashMap是线程不安全的,在多线程环境下,使用Hashmap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap。2.HashTableHashTable和HashMap的实现原理几乎一样,差别无非是1、HashTable不允许key和value为null2、HashTable是线程安全的但是HashTable线程安全的策略实现代价却太大了,简单粗暴,get/put所有相关操作都是synchronized的,这相当于给整个哈希表加了一把大锁。多线程访问时候,只要有一个线程访问或操作该对象,那其他线程只能阻塞,相当于将所有的操作串行化,在竞争激烈的并发场景中性能就会非常差。3.ConcurrentHashMap主要就是为了应对hashmap在并发环境下不安全而诞生的,ConcurrentHashMap的设计与实现非常精巧,大量的利用了volatile,final,CAS等lock-free技术来减少锁竞争对于性能的影响。我们都知道Map一般都是数组+链表结构(JDK1.8该为数组+红黑树)。ConcurrentHashMap避免了对全局加锁改成了局部加锁操作,这样就极大地提高了并发环境下的操作速度。
jdk1.7与jdk1.8中HashMap区别
1.最重要的一点是底层结构不一样,1.7是数组+链表,1.8则是数组+链表+红黑树结构;2.jdk1.7中当哈希表为空时,会先调用inflateTable()初始化一个数组;而1.8则是直接调用resize()扩容;3.插入键值对的put方法的区别,1.8中会将节点插入到链表尾部,而1.7中是采用头插;4.jdk1.7中的hash函数对哈希值的计算直接使用key的hashCode值,而1.8中则是采用key的hashCode异或上key的hashCode进行无符号右移16位的结果,避免了只靠低位数据来计算哈希时导致的冲突,计算结果由高低位结合决定,使元素分布更均匀;5.扩容时1.8会保持原链表的顺序,而1.7会颠倒链表的顺序;而且1.8是在元素插入后检测是否需要扩容,1.7则是在元素插入前;6.jdk1.8是扩容时通过hash&cap==0将链表分散,无需改变hash值,而1.7是通过更新hashSeed来修改hash值达到分散的目的;7.扩容策略:1.7中是只要不小于阈值就直接扩容2倍;而1.8的扩容策略会更优化,当数组容量未达到64时,以2倍进行扩容,超过64之后若桶中元素个数不小于7就将链表转换为红黑树,但如果红黑树中的元素个数小于6就会还原为链表,当红黑树中元素不小于32的时候才会再次扩容。