HashMap和可见性 [英] HashMap and visibility
问题描述
HashMap的javadoc 指出:如果在迭代器创建后的任何时候,结构上都会修改映射,除了通过迭代器自己的remove方法外,任何时候迭代器都会抛出一个ConcurrentModificationException。
我构建了一个示例代码,基于规范,应该几乎立即失败并抛出ConcurrentModificationException; / p>
- 它确实无法像Java 7那样立即失败
- ,但它(似乎)总是与Java 6(即它不会抛出承诺的异常)。
注意:它有时不会在Java 7中失败(比如1次20) - 我想它与线程调度有关(即2个runnables不交错)。
我错过了什么吗?为什么使用Java 6运行的版本不会抛出ConcurrentModificationException?
实际上,有2个Runnable任务并行运行(countdownlatch用于使他们开始大约在同一时间):
- 一个是将项目添加到地图
- 另一个是遍历地图,读取键并将它们放入数组中。
然后主线程检查已经有多少个键添加到数组中。
Java 7典型输出(迭代立即失败):
java.util.ConcurrentModificationException
MAX i = 0
<强大的Java 6典型输出<整个迭代过程和数组包含所有添加的键>:
blockquote>
MAX i = 99
使用的代码:
public class Test1 {
public static void main(String [] args)throws InterruptedException {
final int SIZE = 100;
最终地图< Integer,Integer> map = new HashMap< Integer,Integer>();
map.put(1,1);
map.put(2,2);
map.put(3,3);
final int [] list = new int [SIZE];
final CountDownLatch start = new CountDownLatch(1);
Runnable put = new Runnable(){
@Override
public void run(){
try {
start.await();
for(int i = 4; i< SIZE; i ++){
map.put(i,i);
}
} catch(Exception ex){
}
}
};
Runnable iterate = new Runnable(){
@Override
public void run(){
try {
start.await();
int i = 0; (Map.Entry< Integer,Integer> e:map.entrySet()){
list [i ++] = e.getKey();
Thread.sleep(1);
}
} catch(Exception ex){
ex.printStackTrace();
}
}
};
ExecutorService e = Executors.newFixedThreadPool(2);
e.submit(put);
e.submit(iterate);
e.shutdown();
start.countDown();
Thread.sleep(100);
for(int i = 0; i< SIZE; i ++){
if(list [i] == 0){
System.out.println(MAX i =+一世);
休息;
}
}
}
}
:在x86机器上使用JDK 7u11和JDK 6u38(64位版本)。
如果我们将看看HashMap
来源并在Java 6和Java 7之间进行比较,我们将看到如此有趣的区别:
瞬态Java6中的volatile int modCount;
,Java7中的transient int modCount;
。
我相信这是由于这种原因引起的代码的不同行为:
if(modCount!= expectedModCount)
抛出新的ConcurrentModificationException();
UPD:在我看来,这是一个已知的Java 6 / 7错误: http://bugs.sun.com/bugdatabase/view_bug.do ?bug_id = 6625725 已在最新的Java7中修复。
UPD-2: @Renjith先生说,他只是经过测试,并没有发现HashMaps实现的行为有任何不同。但我也只是测试过。
我的测试是:
$ b <1>我创建了HashMap2
类,它是Java 6中HashMap
的绝对副本。
重要的是我们需要在这里介绍2个新字段:
$ p $transient volatile Set< K> keySet = null;
和
瞬态易失性集合< V> values = null;
2)然后我使用
HashMap2
测试这个问题并在Java 7下运行它
结果:它在
Java 6
,即没有任何ConcurentModificationException
。
<这一切都证明了我的猜想。 Q.E.D。
HashMap's javadoc states:
if the map is structurally modified at any time after the iterator is created, in any way except through the iterator's own remove method, the iterator will throw a ConcurrentModificationException.
I built a sample code that, based on the specification, is supposed to fail almost immediately and throw a ConcurrentModificationException;
- It does fail immediately as expected with Java 7
- but it (seems to) always work with Java 6 (i.e. it does not throw the promised exception).
Note: it sometimes does not fail with Java 7 (say 1 time out of 20) - I guess it has to do with thread scheduling (i.e. the 2 runnables are not interleaved).
Am I missing something? Why does the version run with Java 6 not throw a ConcurrentModificationException?
In substance, there are 2 Runnable tasks running in parallel (a countdownlatch is used to make them start approximately at the same time):
- one is adding items to the map
- the other one is iterating over the map, reading the keys and putting them into an array
The main thread then checks how many keys have been added to the array.
Java 7 typical output (the iteration fails immediately):
java.util.ConcurrentModificationException
MAX i = 0Java 6 typical output (the whole iteration goes through and the array contains all the added keys):
MAX i = 99
Code used:
public class Test1 { public static void main(String[] args) throws InterruptedException { final int SIZE = 100; final Map<Integer, Integer> map = new HashMap<Integer, Integer>(); map.put(1, 1); map.put(2, 2); map.put(3, 3); final int[] list = new int[SIZE]; final CountDownLatch start = new CountDownLatch(1); Runnable put = new Runnable() { @Override public void run() { try { start.await(); for (int i = 4; i < SIZE; i++) { map.put(i, i); } } catch (Exception ex) { } } }; Runnable iterate = new Runnable() { @Override public void run() { try { start.await(); int i = 0; for (Map.Entry<Integer, Integer> e : map.entrySet()) { list[i++] = e.getKey(); Thread.sleep(1); } } catch (Exception ex) { ex.printStackTrace(); } } }; ExecutorService e = Executors.newFixedThreadPool(2); e.submit(put); e.submit(iterate); e.shutdown(); start.countDown(); Thread.sleep(100); for (int i = 0; i < SIZE; i++) { if (list[i] == 0) { System.out.println("MAX i = " + i); break; } } } }
Note: using JDK 7u11 and JDK 6u38 (64 bits version) on an x86 machine.
解决方案If we will look into
HashMap
sources and compare them between Java 6 and Java 7 we will see such interesting difference:transient volatile int modCount;
in Java6 and justtransient int modCount;
in Java7.I'm sure that it is cause for different behavior of mentioned code due to this:
if (modCount != expectedModCount) throw new ConcurrentModificationException();
UPD: It seems to me, that this is a known Java 6/7 bug: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6625725 which was fixed in latest Java7.
UPD-2: Mr. @Renjith said, that he just tested and did not found any difference in behavior of HashMaps implementation. But I just tested it too.
My test was:
1) I have created
HashMap2
class, which is absolutely copy ofHashMap
from Java 6.One important thing is we need to introduce here 2 new fields:
transient volatile Set<K> keySet = null;
and
transient volatile Collection<V> values = null;
2) Then I used this
HashMap2
in test of this question and run it under Java 7Result: it works like such test under
Java 6
, i.e. there isn't anyConcurentModificationException
.That all proves my conjecture. Q.E.D.
这篇关于HashMap和可见性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!