首页 新闻 会员 周边 捐助

Hashmap 多线程 put 遍历时候出现循环

0
悬赏园豆:20 [待解决问题]
复制代码
public class Test2 {

    HashMap<Integer, Integer> map = new HashMap<>();
    
    public static void main(String[] args) {
        Test2 test1 = new Test2();
        test1.put_1();
        test1.put_2();
        try {
            Thread.sleep(5000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        test1.print();
    }
    
    public void print() {
        Set<Entry<Integer, Integer>> set = map.entrySet();
        Iterator<Entry<Integer, Integer>> iterator = set.iterator();
        while(iterator.hasNext()){
            Entry<Integer, Integer> e = iterator.next();
            System.out.println(e.getKey() + "---" + e.getValue());
        }
    }

    public void put_1() {
        T1 t = new T1();
        Thread thread = new Thread(t);
        thread.start();
    }
    
    public void put_2() {
        T2 t = new T2();
        Thread thread = new Thread(t);
        thread.start();
    }
    
    class T2 implements Runnable {
        @Override
        public void run() {
            for (int i = 100000; i < 200000; i++) {
                map.put(i, i);
            }
        }
    }
    
    class T1 implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 100000; i++) {
                map.put(i, i);
            }
        }
    }
    
}
复制代码

这是测试代码。

下面是输出

.....上面忽略
952---952
952---952
952---952
952---952
952---952
952---952
952---952
952---952
952---952
952---952
....无限循环

我知道是并发 resize 的时候出的问题。就是想不通为何会单个元素(这里的952)会形成闭合。

哪位大神能给举个例子解释一下。

问题补充:

没有人知道什么原因吗??大神在哪里。。

loveeeeee的主页 loveeeeee | 初学一级 | 园豆:125
提问于:2016-08-04 11:36
< >
分享
所有回答(1)
0

http://www.cnblogs.com/ITtangtang/p/3966467.html

分析已经有了

之奇一昂 | 园豆:1421 (小虾三级) | 2016-08-04 12:39

我也知道是这里出的问题。请解释详细一些好吗。举个例子。想了几个小时想不通,自认为还不笨。

.....上面忽略
952---952
952---952
952---952
952---952
952---952
952---952
952---952
952---952
952---952
952---952
....无限循环

您发的链接看了,类似的问题我早已Google查了很多了,例子也都看过。

没有发现我的这种情况。单个元素形成闭合的。

支持(0) 反对(0) loveeeeee | 园豆:125 (初学一级) | 2016-08-04 13:24

@熊猫烧香_x: http://coolshell.cn/articles/9606.html 首先,我也没仔细想过,只能和你一块讨论,前面这篇讲得比较好,比如952这个rehash的时候

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

第一个线程 next = e.next 现在next是952了,但是挂起了。另一个线程也拿的952走到这一步了,而且继续起,并把e.next = newTable[i]和newTable[i] = e执行了,现在newTable[i] = 952了,线程一开始执行,e.next = newTable[i],这时候就是952.net = 952了。。

至于为什么会有两个线程进行rehash呢,我觉得是两个线程同时放952,导致两个线程都觉得该扩容了,然后各自分别开始扩容,但是由于数据的确定性,导致两个线程的数据整理方式一样,数据的位置也一样,唯一会出问题的就是当两个线程拿同一个数据往里面放的时候,如果每一个都只有一步,应该也不会出问题, A B 或 B A都可以,但是显示不是原子的 A0 A1 B0 B1 或 B0 B1 A0 A1都可以保证程序正常使用,但是 A0 B0 B1 A1 这样或 B0 A0 A1 B1 这样的乱序就出问题了。  两个线程为什么操作的是同一个TABLE呢?我觉得因为是hashmap里面的成员变量,table = new Entry[n] 这样的操作不管几个线程执行,最后都是最后那个线程有效。不知道分析的对不对,有了想法多交流交流吧!;)

支持(0) 反对(0) 之奇一昂 | 园豆:1421 (小虾三级) | 2016-08-05 08:59

@之奇一昂: 不对,不是放952的时候觉得该扩容了,而是放别的数据的时候,那时候952已经在hashmap里面了,只是rehash的时候冲突了

支持(0) 反对(0) 之奇一昂 | 园豆:1421 (小虾三级) | 2016-08-05 09:02

@之奇一昂: 第一个线程 next = e.next 现在next是952了,但是挂起了。另一个线程也拿的952走到这一步了,而且继续起,并把e.next = newTable[i]和newTable[i] = e执行了,现在newTable[i] = 952了

第一个线程挂起的时候,next 指向 952.

线程二启动后,也走到了这一步,e.next = newTable[i] 想来这里 newTable[i] 中还没有值呢。

newTable[i] = e,为什么会是952呢?

支持(0) 反对(0) loveeeeee | 园豆:125 (初学一级) | 2016-08-05 12:56

@之奇一昂: http://news.cnblogs.com/n/177544/  类似的文章看过很多。没有一个是我这种单个元素形成闭合回路的。

支持(0) 反对(0) loveeeeee | 园豆:125 (初学一级) | 2016-08-05 12:59

@熊猫烧香_x:

两个线程同时要把952放进新的数组里面,而且一块都走到了要把952这个ENTRY放到TABLE里面了。
线程1 2 现在都拿到了E(同一个E)
线程1 暂停,
线程2,把E的NEXT 指向 TABLE[I],然后把TABLE[I]换成自己,这个时候TABLE[I]成了E也就是952.
线程1,把E的NEXT 指向 TABLE[I](也就是952.NEXT = 952了),然后把TABLE[I]换成自己(本来就已经是自己了)。这时候TABLE[I]是E,而E.NEXT = E

现实中举个例子珠光宝气

支持(0) 反对(0) 之奇一昂 | 园豆:1421 (小虾三级) | 2016-08-05 14:07

@之奇一昂: 只要是多线程,不管是几个元素,都会出这个问题

    public static void main(String[] args) throws InterruptedException {
        Map<Integer, Integer> map = new HashMap<Integer, Integer>();
        Thread thread = new Thread(() -> {
            for(int i = 0; i < 1000; i ++) {
                map.put(i, i);
            }
        });
        Thread thread2 = new Thread(() -> {
            for(int i = 0; i < 1000; i ++) {
                map.put(i, i);
            }
        });
        thread.start();
        thread2.start();
        Thread.sleep(1000);
        System.out.println(map.keySet().size());
    }

你运行几次看看结果,每次都不一样,要是打印值的话,很大可能会少,因为多个线程操作的话

你把TABLE[I] = E0, 我把TABLE[I] = E1,这样 E0那个就被挤了,还没有来得及有一个E的NEXT指向它

支持(0) 反对(0) 之奇一昂 | 园豆:1421 (小虾三级) | 2016-08-05 14:24

@之奇一昂: 我还是觉得不对。两个元素三个元素造成循环我都能理解。唯独一个元素循环搞不明白。

你说:线程1,把E的NEXT 指向 TABLE[I](也就是952.NEXT = 952了)。

线程1,把E的NEXT 指向 TABLE[I] ,这个 TABLE[I] 是那个 newTable,是 resize 的时候新 new 的,并不是和线程2共用,怎么会变成 952呢?

支持(0) 反对(0) loveeeeee | 园豆:125 (初学一级) | 2016-08-05 18:16

@之奇一昂: 事实上是 newTable 并不是两个线程共用,你的理解应该不对。

支持(0) 反对(0) loveeeeee | 园豆:125 (初学一级) | 2016-08-05 18:29

@熊猫烧香_x: 我看了一下,你说的对,newTable是在线程内的。单一元素重复可能是由于 Entry在transfer的时候是线程共享的,NEXT会乱导致的?目前还没有想到一个合适的操作顺序,周一各种忙,过一会再想想

支持(0) 反对(0) 之奇一昂 | 园豆:1421 (小虾三级) | 2016-08-08 11:35

@之奇一昂: 对啊,好几个小时想破脑袋了都,其实不用纠结,但是就是想找个例子出来。

支持(0) 反对(0) loveeeeee | 园豆:125 (初学一级) | 2016-08-08 19:09
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册