Java集合框架之十三-------------HashMap的扩容与线程安全问题

HashMap扩容中,hash & oldCap的作用,观察扩容前和扩容后下标的变化

原来的0101和10101在length=16的时候,通过hash&length-1的方法,计算出来都是0101;但是在扩容后即length=32时,hash&length -1 的方法计算出来为0101和10101;这就说明扩容后有些数据需要移动到原来index+oldcap的位置;为了观察0101和10101的区别发现只有最高位是0和1的区别;为了避免进行重复的hash&length -1计算,此时采用了hash&oldCap的方法,将10101和10000;也就是取最高位的值;来判断是否需要移动位置,节省时间。

 

1.7的hashMap扩容成环问题

void transfer(Entry[] newTable)

{

    Entry[] src = table;

    int newCapacity = newTable.length;

    //下面这段代码的意思是:

    //  从OldTable里摘一个元素出来,然后放到NewTable中

    for (int j = 0; j < src.length; j++) {

        Entry<K,V> e = src[j];

        if (e != null) {

            src[j] = null;

            do {

                Entry<K,V> next = e.next;

                int i = indexFor(e.hash, newCapacity);

                e.next = newTable[i];

                newTable[i] = e;

                e = next;

            } while (e != null);

        }

    }

}

 

上述的while循环中,线程2对newTab的操作是会对线程1产生影响的

在1.8中,具体如下

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) {

          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);

     if (loTail != null) {

           loTail.next = null;

           newTab[j] = loHead;

       }

        if (hiTail != null) {

            hiTail.next = null;

            newTab[j + oldCap] = hiHead;

        }

线程1执行到next=e.next,中断;然后线程2开始执行。

当线程2执行完毕后,此时的hiHead与loHead属于局部变量,也就是线程2对其做的操作对线程1没有影响,并且该段代码在循环体中没有对newTab进行操作。

线程1此时恢复执行,hiHead与loHead都恢复成空,即重新执行一遍循环体,在进行newTable的赋值,会覆盖掉上次的结果,不过结果是一样的。

 

但是1.8也存在不安全的问题,在进行添加元素和删除元素的时候,如果该元素不存在,会进行size++;

此时如果两个线程同时执行,按理说size=3;但实际上会出现2;线程之间只对自己的副本进行操作,所以会引起这个问题。

HashMap扩容中,hash & oldCap的作用,观察扩容前和扩容后下标的变化

原来的0101和10101在length=16的时候,通过hash&length-1的方法,计算出来都是0101;但是在扩容后即length=32时,hash&length -1 的方法计算出来为0101和10101;这就说明扩容后有些数据需要移动到原来index+oldcap的位置;为了观察0101和10101的区别发现只有最高位是0和1的区别;为了避免进行重复的hash&length -1计算,此时采用了hash&oldCap的方法,将10101和10000;也就是取最高位的值;来判断是否需要移动位置,节省时间。

 

1.7的hashMap扩容成环问题

void transfer(Entry[] newTable)

{

    Entry[] src = table;

    int newCapacity = newTable.length;

    //下面这段代码的意思是:

    //  从OldTable里摘一个元素出来,然后放到NewTable中

    for (int j = 0; j < src.length; j++) {

        Entry<K,V> e = src[j];

        if (e != null) {

            src[j] = null;

            do {

                Entry<K,V> next = e.next;

                int i = indexFor(e.hash, newCapacity);

                e.next = newTable[i];

                newTable[i] = e;

                e = next;

            } while (e != null);

        }

    }

}

 

上述的while循环中,线程2对newTab的操作是会对线程1产生影响的

在1.8中,具体如下

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) {

          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);

     if (loTail != null) {

           loTail.next = null;

           newTab[j] = loHead;

       }

        if (hiTail != null) {

            hiTail.next = null;

            newTab[j + oldCap] = hiHead;

        }

线程1执行到next=e.next,中断;然后线程2开始执行。

当线程2执行完毕后,此时的hiHead与loHead属于局部变量,也就是线程2对其做的操作对线程1没有影响,并且该段代码在循环体中没有对newTab进行操作。

线程1此时恢复执行,hiHead与loHead都恢复成空,即重新执行一遍循环体,在进行newTable的赋值,会覆盖掉上次的结果,不过结果是一样的。

 

但是1.8也存在不安全的问题,在进行添加元素和删除元素的时候,如果该元素不存在,会进行size++;

此时如果两个线程同时执行,按理说size=3;但实际上会出现2;线程之间只对自己的副本进行操作,所以会引起这个问题。

猜你喜欢

转载自blog.csdn.net/huangwei18351/article/details/82721818