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)会形成闭合。
哪位大神能给举个例子解释一下。
没有人知道什么原因吗??大神在哪里。。
http://www.cnblogs.com/ITtangtang/p/3966467.html
分析已经有了
我也知道是这里出的问题。请解释详细一些好吗。举个例子。想了几个小时想不通,自认为还不笨。
.....上面忽略 952---952 952---952 952---952 952---952 952---952 952---952 952---952 952---952 952---952 952---952 ....无限循环
您发的链接看了,类似的问题我早已Google查了很多了,例子也都看过。
没有发现我的这种情况。单个元素形成闭合的。
@熊猫烧香_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] 这样的操作不管几个线程执行,最后都是最后那个线程有效。不知道分析的对不对,有了想法多交流交流吧!;)
@之奇一昂: 不对,不是放952的时候觉得该扩容了,而是放别的数据的时候,那时候952已经在hashmap里面了,只是rehash的时候冲突了
@之奇一昂: 第一个线程 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呢?
@之奇一昂: http://news.cnblogs.com/n/177544/ 类似的文章看过很多。没有一个是我这种单个元素形成闭合回路的。
@熊猫烧香_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
现实中举个例子珠光宝气
@之奇一昂: 只要是多线程,不管是几个元素,都会出这个问题
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指向它
@之奇一昂: 我还是觉得不对。两个元素三个元素造成循环我都能理解。唯独一个元素循环搞不明白。
你说:线程1,把E的NEXT 指向 TABLE[I](也就是952.NEXT = 952了)。
线程1,把E的NEXT 指向 TABLE[I] ,这个 TABLE[I] 是那个 newTable,是 resize 的时候新 new 的,并不是和线程2共用,怎么会变成 952呢?
@之奇一昂: 事实上是 newTable 并不是两个线程共用,你的理解应该不对。
@熊猫烧香_x: 我看了一下,你说的对,newTable是在线程内的。单一元素重复可能是由于 Entry在transfer的时候是线程共享的,NEXT会乱导致的?目前还没有想到一个合适的操作顺序,周一各种忙,过一会再想想
@之奇一昂: 对啊,好几个小时想破脑袋了都,其实不用纠结,但是就是想找个例子出来。