HashMap线程安全问题

以下参考jdk1.7
扩容resize就是重新计算容量,向HashMap中不停的添加元素,而HashMap内部的数组无法装载时,就需要扩大数组长度。而数组无法自动扩容,那么HashMap采用的是用一个新的数组代替已有的容量小的数组。
首先看一下扩容resize的源码

void resize(int newCapacity) {   //传入新的容量
    Entry[] oldTable = table;    //引用扩容前的Entry数组
    int oldCapacity = oldTable.length;         
    if (oldCapacity == MAXIMUM_CAPACITY) {  //扩容前的数组大小如果已经达到最大(2^30)了
        threshold = Integer.MAX_VALUE; //修改阈值为int的最大值(2^31-1),这样以后就不会扩容了
       return;
    }

    Entry[] newTable = new Entry[newCapacity];  //初始化一个新的Entry数组
    transfer(newTable);                         //!!将数据转移到新的Entry数组里
    table = newTable;                           //HashMap的table属性引用新的Entry数组
threshold = (int)(newCapacity * loadFactor);//修改阈值
}

这就是用一个容量大的数组来代替已有的小数组,transfer()方法将原油Entry数组的元素拷贝到新的Entry数组里
这是transfer的源码,注意do while语句

 void transfer(Entry[] newTable) {
     Entry[] src = table;                   //src引用了旧的Entry数组
     int newCapacity = newTable.length;
     for (int j = 0; j < src.length; j++) { //遍历旧的Entry数组
         Entry<K,V> e = src[j];             //取得旧Entry数组的每个元素
         if (e != null) {
             src[j] = null;//释放旧Entry数组的对象引用(for循环后,旧的Entry数组不再引用任何对象)
             do {
                 Entry<K,V> next = e.next;
                 int i = indexFor(e.hash, newCapacity); //!!重新计算每个元素在数组中的位置
                 e.next = newTable[i]; //标记[1]
                 newTable[i] = e;      //将元素放在数组上
                 e = next;             //访问下一个Entry链上的元素
             } while (e != null);
         }
     }
 } 

看到源码可以知道do while从链表的头开始一直到取到链表最后一个元素(尾为null),否则一直循环
newTable[i]的引用赋给了e.next,也就是使用了单链表的头插入方式,同一位置上新元素总会被放在链表的头部位置

public class HashMapInfiniteLoop {  

    private static HashMap<Integer,String> map = new HashMap<Integer,String>(2,0.75f);  
    public static void main(String[] args) {  
        map.put(5, "C");  

        new Thread("Thread1") {  
            public void run() {  
                map.put(7, "B");  
                System.out.println(map);  
            };  
        }.start();  
        new Thread("Thread2") {  
            public void run() {  
                map.put(3, "A);  
                System.out.println(map);  
            };  
        }.start();        
    }  
} 

在这里插入图片描述
简单模拟一下扩容过程,这里设定hash算法就是简单的key mod一下表的大小(数组长度)
其中HashMap初始size=2,负载因子loadFactor=1(threshold = length * Load factor)
存入 key为 5、7、3,所以5%2,7%2,3%2都为1,但是threshold为2
在这里插入图片描述
key=3插入后键值对数量>threshold进行resize,将数组大小扩容为原来的两倍后进行transfer重新计算hash值以及在数组中的位置
3、5、7新的下标分别为 3,1,3
在这里插入图片描述
通过transfer代码可以看到,是从链表的头开始依次一直到取到链表最后一个元素
当thread1获取到第一个entry(key=3)时,next指向entry(key=7)
而thread2已经先一步执行完了所有步骤完成了rehash,即此时entry(key=7)的next指向了entry(key=3),此时thread1就会陷入环形链表中e.next一直不为null,一直陷入dowhile循环中。

参考
Java 8系列之重新认识HashMap

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值