关于HashMap为什么是线程不安全的原因

我们知道hashmap的扩容因子是0.75,如果hashmap的数组长度已经使用了75%就会引起扩容,会新申请一个长度为原来两倍的桶数组,

然后将原数组的元素重新映射到新的数组中,原有数据的引用会逐个被置为null。就是在resize()扩容的时候会造成线程不安全。

另外当一个新节点想要插入hashmap的链表时,在jdk1.8之前的版本是插在头部,在1.8后是插在尾部。

那么hashmap什么时候进行扩容呢?当hashmap中的元素个数超过数组大小*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,也就是说,默认情况下,数组大小为16,

那么当hashmap中元素个数超过16*0.75=12的时候,就把数组的大小扩展为2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,

所以如果我们已经预知hashmap中元素的个数,那么预设数组的大小能够有效的提高hashmap的性能。

比如说,我们有1000个元素new HashMap(1000), 但是理论上来讲new HashMap(1024)更合适,不过上面annegu已经说过,即使是1000,hashmap也自动会将其设置为1024。

但是new HashMap(1024)还不是更合适的,因为0.75*1024 < 1000, 也就是说为了让0.75 * size > 1000, 我们必须这样new HashMap(2048)才最合适,避免了resize的问题。

不安全原因:

(1)在put的时候,因为该方法不是同步的,假如有两个线程A,B它们的put的key的hash值相同,不论是从头插入还是从尾插入,假如A获取了插入位置为x,

    但是还未插入,此时B也计算出待插入位置为x,则不论AB插入的先后顺序肯定有一个会丢失;

(2)在扩容的时候,jdk1.8之前是采用头插法,当两个线程同时检测到hashmap需要扩容,在进行同时扩容的时候有可能会造成链表的循环,

     主要原因就是,采用头插法,新链表与旧链表的顺序是反的,在1.8后采用尾插法就不会出现这种问题,同时1.8的链表长度如果大于8就会转变成红黑树。



猜你喜欢

转载自www.cnblogs.com/ZJOE80/p/12563552.html