HashMap分析

HashMap为什么线程不安全

JDK7实现

当entry数组进行rehash扩容的时候会形成循环列表导致后续map.get(key)的时候陷入死循环当entry数组进行rehash扩容的时候会形成循环列表导致后续map.get(key)的时候陷入死循环。
直接上代码:

  /**
    * Transfers all entries from current table to newTable.
    */
   void transfer(Entry[] newTable, boolean rehash) {
       int newCapacity = newTable.length;
       for (Entry<K,V> e : table) {
           while(null != e) {
               Entry<K,V> next = e.next;
               if (rehash) {
                   e.hash = null == e.key ? 0 : hash(e.key);
               }
               int i = indexFor(e.hash, newCapacity);
               e.next = newTable[i];
               newTable[i] = e;
               e = next;
           }
       }
   }

上面代码是entry数组扩容后将原来的数据村放入新的entry数组中
假设现在有A,B两个线程,同时向map中put元素,长度刚好到了扩容临界点,此时数组进行扩容。
在这里插入图片描述
线程1,线程2同时进入扩容,两个线程都会新建newTable。
在这里插入图片描述
线程1拿到cpu时间片执行transfer()方法之后newTable的结构为:
在这里插入图片描述
此时线程1尚未将newTable赋值给table,这时时间片切换,线程2拿到cpu执行权,进行操作,第一次循环结果:
在这里插入图片描述
此时next为b,b!=null,再次进入循环(第二次循环)
在这里插入图片描述
此时next为a,a!=null,第三次进入循环:
在这里插入图片描述
最终next为null,跳出循环。
节点a和b互相引用,形成了一个环,当在数组该位置get寻找对应的key时,就发生了死循环。

另外,如果线程2把newTable设置成到内部的table,节点c的数据就丢了,看来还有数据遗失的问题。

JDK8实现

java8中对hashmap进行了比较大的更改,加入了红黑树的实现等,我们先抛开这些,单纯就扩容的方法来进行探讨。

final Node<K,V>[] resize() {
       Node<K,V>[] oldTab = table;
       int oldCap = (oldTab == null) ? 0 : oldTab.length; //旧数组长度
       int oldThr = threshold;
       int newCap, newThr = 0;
       if (oldCap > 0) {
           if (oldCap >= MAXIMUM_CAPACITY) {
               threshold = Integer.MAX_VALUE;
               return oldTab;
           }
           //MAXIMUM_CAPACITY =1 << 30,最大值为2^30次方
           else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                    oldCap >= DEFAULT_INITIAL_CAPACITY)
               newThr = oldThr << 1; // double threshold  扩容原长度*2
       }
       else if (oldThr > 0) // initial capacity was placed in threshold
           newCap = oldThr;
       else {               // zero initial threshold signifies using defaults
           newCap = DEFAULT_INITIAL_CAPACITY;
           newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
       }
       if (newThr == 0) {
           float ft = (float)newCap * loadFactor;
           newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                     (int)ft : Integer.MAX_VALUE);
       }
       threshold = newThr;
       @SuppressWarnings({"rawtypes","unchecked"})
       Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
       table = newTab;
       if (oldTab != null) {
           for (int j = 0; j < oldCap; ++j) {
               Node<K,V> e;
               if ((e = oldTab[j]) != null) {
                   oldTab[j] = null;
                   if (e.next  ==  null)
                       newTab[e.hash & (newCap - 1)] = e;
                   else if (e instanceof TreeNode)
                       ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);//红黑树的逻辑跳过
                   else { // preserve order
                       Node<K,V> loHead = null, loTail = null;
                       Node<K,V> hiHead = null, hiTail = null;
                       Node<K,V> next;
                       do {
                           next = e.next;
                           if ((e.hash & oldCap) == 0) {
                               //e.hash & oldCap 方法切断元素在扩容的时候是否需要移动位置
                               //17 & 16 ==1,说明在扩容的时候hash为17这个node需要移动位置
                               //12 & 16 ==0,说明在扩容的时候hash为12这个node不需要移动位置
                               //32 & 16 ==0,说明在扩容的时候hash为32的node不需要移动位置
                               if (loTail == null)
                                   loHead = e;
                               else
                                   loTail.next = e;
                               loTail = e;
                           }
                           else {
                               if (hiTail == null)
                                   hiHead = e;
                               else
                                   hiTail.next = e;
                               hiTail = e;
                           }
                       } while ((e = next) != null);
                       //循环的部分解决了1.7以前原链a-》b-》c rehash之后变成c->b->a的问题,从根本上解决了产生死链的问题
                       if (loTail != null) {
                           loTail.next = null;
                           newTab[j] = loHead;
                       }
                       if (hiTail != null) {
                           hiTail.next = null;
                           newTab[j + oldCap] = hiHead;
                       }
                   }
               }
           }
       }
       return newTab;
   }

但是这并不保证hashmap就是线程安全的,在多线程的情况下hashmap还是不安全,可能会丢失数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值