HashMap在jdk1.8也会出现死循环的问题(实测)

昨天在面试的时候,面试官问到HashMap在多线程并发的情况下怎么出现死循环的?
我当时非常有底气的回答:Hashmap在jdk1.8之前多线程同时进行put操作,并且同时进行扩容的时候可能会出现链表环,导致死循环的发生。
面试官紧接着问:那1.8就不会出现死循环了吗?
底气瞬间消失,思考一会:1.8在多线程的情况下HashMap会出现数据丢失的问题,比如…(给面试官举了个栗子)。
面试官毫无波澜,好像没有get到答案的样子问:那jdk1.8的HashMap是不是就不会出现死循环了?
我当时愣了一下,心想难道1.8也会出现死循环…考虑一会,最后决定坚持自己,但是已经没有了底气:是的。
面试官露出了开心的样子:嗯,好的。(看来肯定是说错了)


介绍完前因后果,那么来看一下Hashmap在jdk1.8会不会出现死循环的情况。

测试代码

import java.util.*;
public class MainTest {
	Map<String,String> map = new HashMap<>();
	
    public void hashMapTest() {
        for (int i = 0;i < 500;i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0;j < 500;j++) {
                        map.put(Thread.currentThread().getName() + "-" + j, j+"");
                    }
                }
            }).start();
        }
        try {
            Thread.sleep(2000);
//            map.forEach((x,y) -> System.out.println(x));
            System.out.println(map.size());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

如果执行之后发现,程序正常执行结束,那就再多执行几次,总有那么一次会出现问题,或者增加线程数量试试。当出现程序一直没有结束,来查看一下我们的cpu使用情况
cpu使用情况
发现cpu使用率99.9%,符合死循环的情况。
然后再使用 jpsjstack 命令查看一下进程的状态
进程状态
看一下HashMap的1948行

final void treeify(Node<K,V>[] tab) {
            TreeNode<K,V> root = null;
            for (TreeNode<K,V> x = this, next; x != null; x = next) {
                next = (TreeNode<K,V>)x.next;
                x.left = x.right = null;
                if (root == null) {
                    x.parent = null;
                    x.red = false;
                    root = x;
                }
                else {
                    K k = x.key;
                    int h = x.hash;
                    Class<?> kc = null;
                    //说明线程在这个for循环中一直没有返回,导致了死循环
                    for (TreeNode<K,V> p = root;;) {
                        int dir, ph;
                        K pk = p.key;
                        if ((ph = p.hash) > h)
                            dir = -1;
                        else if (ph < h)
                            dir = 1;
                        else if ((kc == null &&
                                  (kc = comparableClassFor(k)) == null) ||
                                 (dir = compareComparables(kc, k, pk)) == 0)
                            dir = tieBreakOrder(k, pk);

                        TreeNode<K,V> xp = p;
                        if ((p = (dir <= 0) ? p.left : p.right) == null) {
                            x.parent = xp;
                            if (dir <= 0)
                                xp.left = x;
                            else
                                xp.right = x;
                            root = balanceInsertion(root, x);
                            break;
                        }
                    } // 1948行
                }
            }
            moveRootToFront(tab, root);
        }

这个方法看出,说明Hashmap在jdk1.8的时候也会出现死循环的情况,是在链表转换为树的时候for循环一直无法跳出,导致死循环。
不止这些,经过多次测试,发现死循环还会在2239行出现

java.lang.Thread.State: RUNNABLE
	at java.util.HashMap$TreeNode.balanceInsertion(HashMap.java:2239)
	at java.util.HashMap$TreeNode.treeify(HashMap.java:1945)
	at java.util.HashMap$TreeNode.split(HashMap.java:2180)
	at java.util.HashMap.resize(HashMap.java:714)
	at java.util.HashMap.putVal(HashMap.java:663)
	at java.util.HashMap.put(HashMap.java:612)
	at com.luck.ejob.server.MainTest$1.run(MainTest.java:151)
	at java.lang.Thread.run(Thread.java:748)

balanceInsertion这个方法是对树进行一个重新的平衡。
再看下面这种情况

java.lang.Thread.State: RUNNABLE
	at java.util.HashMap$TreeNode.root(HashMap.java:1824)
	at java.util.HashMap$TreeNode.putTreeVal(HashMap.java:1978)
	at java.util.HashMap.putVal(HashMap.java:638)
	at java.util.HashMap.put(HashMap.java:612)
	at com.luck.ejob.server.MainTest$1.run(MainTest.java:151)
	at java.lang.Thread.run(Thread.java:748)
final TreeNode<K,V> root() {
    for (TreeNode<K,V> r = this, p;;) {
        if ((p = r.parent) == null)
            return r;
        r = p; //1824行
    }
}

所以jdk1.8的HashMap在多线程的情况下也会出现死循环的问题,但是1.8是在链表转换树或者对树进行操作的时候会出现线程安全的问题。

复习一下线程安全的定义:

在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。

  • 30
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值