HashMap原理及源码解析

https://www.bilibili.com/video/av73968282?from=search&seid=1400532295507095115

周瑜老师讲的很好

 

https://www.bilibili.com/video/av57047473?from=search&seid=3585940833917369886

45分钟开始,将currentHashMap

 

https://www.bilibili.com/video/av56026765?p=2

HashMap并发的循环链表(循环链表)。5分钟

----根本原因就是扩容之后链表的元素顺寻调换了导致并发的时候出现这个问题。

 

1、hashMap机制

     1.1、jdk1.7

             数组(entity)+链表。

             1.1.1、根据key计算hashCode值,然后对hashCode进行高位运算,最后和数组长度(初始容量)-1进行与运算,得到entity的存储位置。

             1.1.2、多个对象的key计算出来的存储位置有可能一样,就是hash碰撞,这个时候就在相同的位置增加链表,链表扩容采用的是头插法,这样插入效率很高。

             1.1.3、当key为null的时候,这个是指定位置的,就放在entity的第一个下标位置。

                        当相同的key的时候就会替换value的值,同时把oldValue返回。

                        初始容量的时候,是2的N次方,主要是在后期的hashCode的 &运算的时候,增强散列性。比如如果是0001 0000减去就是0000 1111,这样进行位运算就取决于hashCode的位运算之后的值,可以让hashCode的高位参与运算,增强散列性,是单个链表不至于太长。如果指定初始容量是10,那么初始容量就是最接近10的2的N次方,16。

             1.1.4、当数组扩容的时候一般是两倍的扩容,原本的链表要么还在原来的tabel[i]下面,要么就在2倍的tabel[i]下面。

      1.2、jdk1.8

            数组+链表+红黑树

            1、高位运算没有那么复杂了,因为已经有红黑树来保证查询效率了。

                 采用的尾部插入元素的方法,因为总是要遍历链表数量来确定是否树化,这样就可以顺便插入元素到尾部。多线程的时候就不会出现循环链表了。扩容的时候计算index也简单了,直接拿hash的值,如果再tabel的长度位是0的话,tabel[i]就不变,如果是1,就直接加tabel的长度位作为下标

            2、红黑树是大于8,就树化,低于6就链表化。之所以不一样是因为,避免再临界值附近来回的转换。

            3、树化也不是大于8就直接开始,还有一个就是数组长度要大于默认值64才会进行树化,因为数组扩容也是链表长度降低。

 

2、volidate和syn关键字

      https://blog.csdn.net/qq_41807597/article/details/85162083

      多线程编码中,当jvm开始优化的时候,强制线程再使用volidate修饰的变量的时候,一定要从主寄存器里面拿。不从副本里面获取,并且变化直接操作主寄存器。

      问题:

           2.1、数据混乱。当线程1从主寄存器中拿了数据20,再操作还未回写主寄存器的时候,线程2也拿到了数据20,当线程1吧数据减1后19写入主寄存器。线程2减1有回写,还是19,但是真正的值应该是18。

           2.2、

 

   

posted @ 2019-11-06 12:46  傻杜  阅读(175)  评论(0编辑  收藏  举报