此篇博客源码是基于jdk 1.7
主要讲的知识点罗列如下:
1、底层结构
2、扩容机制
3、线程安全问题
底层结构及hash冲突
hashMap底层结构:数组+链表


Entry中保存着HashMap的key和value;next对象是当出现hash碰撞时,用于存储原该entry链表中对象值;

为什么会出现hash冲突?

如图中标注:通过key.hashcode()计算出hash值,如果存储的对象多了,就有可能出现不同的对象计算出来的hash值相同,这时hash冲突就出现了;
如何扩容的呢?
hashMap在构造对象的时候,如果不指定构造参数,会有默认初始值;初始容量为:16,默认加载因子:0.75f;当然啦你也可以自定义初始值,一般在知道容器大小的时候,建议给个初始值大小;
里面有个关键字段:threshold(阈值);这个用来计算何时扩容,threshold=Math.min(capacity *loadFactor, MAXIMUM_CAPACITY +1)

扩容后重新根据key计算hash,获取对应的bucketIndex;
是线程安全的吗?如何保证线程安全?
不是线程安全的,主要原因会出现死循环上;

多线程环境下,线程A当对容器进行扩容的时候,执行到如图所示位置时,线程A时间片被消耗完,线程被挂起;此时: e=3,next=7,e.next = null;


此时线程B获得时间片,执行扩容后完成数据的转换;

此时线程A获取cpu时间片,继续执行剩下的步骤,newTable[i] = e;e = next;执行后如图:

继续执行下一轮循环,此时e=7,从主内存中读取数据发现e.next=3;然后继续此轮循环,结果如下:

然后继续执行下轮循环,此时e = 3,e.next = null; 当执行到e.next = newTable[i]时,e.next = 7,采用头插法后,结果如下,3和7之间形成环形;此时循环结束,此时还不会有死循环的危险;

当容器进行下一轮的扩容或者通过key获取值时等操作时,会发生死循环的危险;
怎么避免线程不安全呢?
1、可以使用hashtable;
2、通过Collections.synchronizedMap() 方法包装;
3、使用ConcurrentHashMap (推荐使用)
总结:HashMap在计算初始容量的时候,如果给了初始值,其实在内部会做个转化,使其容量为2的整数倍;当size>=threshold时,会执行扩容,倍数为当前容量的2倍;在使用的时候要避免出现线程安全的问题,存在并发的时候,使用ConcurrentHashMap来代替;
网友评论