HashMap 1.7 成环问题,demo代码级探究

本文详细解析了Java HashMap在并发环境下插入新元素可能导致环形结构的问题,通过关键代码分析和debug过程展示了成环的条件,并提供了两种测试方法。重点讲解了如何通过Thread级别断点调试和理解线程间的交互。

目录

一、核心代码部分

二、核心代码的两个断点

三、Debug运行

3.1 运行Thread-0部分

3.2 运行Thread-1部分(第一种测试)

3.3 运行Thread-1部分(第二种测试)


一、核心代码部分

首先上代码,如下代码是测试成环的case(切记,需要JDK配置为1.7,我用到的是jdk-7u80-windows-x64.exe版本)

package com.mfanw;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public class MainMap {

    public static void main(String[] args) throws InterruptedException {
        final Map<MyString, String> map = new HashMap<>(8);
        final int count = 6;
        for (int i = 0; i < count; i++) {
            map.put(new MyString("" + i), "" + i);
        }
        Thread[] ts = new Thread[2];
        for (int c = 0; c < 2; c++) {
            final int fc = c;
            ts[c] = new Thread(new Runnable() {
                @Override
                public void run() {
                    String key = (fc + count) + "";
                    map.put(new MyString(key), key); // 此处为断点A
                    System.out.println(map);
                }
            });
            ts[c].start();
        }
        System.out.println("haha");
        Thread.sleep(1000 * 60 * 60 * 24);
    }

}

class MyString {
    public String text;

    public MyString(String text) {
        this.text = text;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        MyString myString = (MyString) o;
        return Objects.equals(text, myString.text);
    }

    /**
     * 确保全部元素都落在HashMap的第0个table内
     *
     * @return 0
     */
    @Override
    public int hashCode() {
        return 0;
    }

    @Override
    public String toString() {
        return text;
    }
}

 

 

 

二、核心代码的两个断点

断点A和断点B:如下图所示

断点C:如下图所示

 

 

 

注意:如下图,确保全部的断点,都是Thread级别(在断点上右击,该配置仅idea需要操作,eclipse默认完美支持Thread断点)

 

 

三、Debug运行

3.1 运行Thread-0部分

Debug运行main方法,可能会首先在断点C内停止,可以跳过。

当运行到断点A停止后,应该可以看到是两个线程被中断。

 

 

切换到 Thread-0 并点击运行,会马上在断点C处中断,手工执行到594行。

注意:目前的table是  5 → 4 → 3 → 2 → 1 → 0

e=5(因为它也要尝试头插法转移元素到newTable)

next=e.next=4

 

3.2 运行Thread-1部分(第一种测试)

承接3.1的操作后进行该步骤

切换到Thread-1,一路F8执行过去,会直接抛出 Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space,原因是已经成环。

 

3.3 运行Thread-1部分(第二种测试)

承接3.1的操作后进行该步骤

切换到Thread-1,一直点击运行,直到断点B处

 

Thread-1的 transfer方法会将 table=5 → 4 → 3 → 2 → 1 → 0 转移到 newTable内,且顺序颠倒过来,因为它是头插法转移,

先转移5,再转移4,再转移3...... 也就是最终 newTable = 0 → 1 → 2 → 3 → 4 → 5

然后Thread-1再头插法插入新元素,最终 newTable = 7 → 0 → 1 → 2 → 3 → 4 → 5

 

切换到Thread-0, 引用3.1章节末尾的注意内容可知  e=5 , next=4

目前的 table是  7 → 0 → 1 → 2 → 3 → 4 → 5 (7是Thread-1刚刚给加进去的)

注意:此处的 newTable 是上层方法 resize内new出来的,所以此处newTable是empty

第一次循环

e.next = newTable[i]; // 5.next = null
newTable[i] = e; // newTable头结点设置为5
e = next; // 设置e=4进入下一次循环

第二次循环

Entry<K,V> next = e.next;  // e=4,  next = 4.next=5
if (rehash) {
    e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity); 
e.next = newTable[i];   // 4.next = newTable的头结点 = 5
newTable[i] = e; // newTable头结点设置为4
e = next; // 设置e=5进入下一次循环

第三次循环

Entry<K,V> next = e.next; // e=5,  next=5.next=null
if (rehash) {
    e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];  // 5.next=newTable的头结点=4,HashMap终于成环了
newTable[i] = e; // newTable头结点设置为5
e = next; // 设置e=null也就无法进入下一次循环了

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值