【发布时间】:2013-01-16 16:31:25
【问题描述】:
如果在迭代器创建后的任何时候对映射进行结构修改,除了通过迭代器自己的 remove 方法之外的任何方式,迭代器都会抛出 ConcurrentModificationException。
我构建了一个示例代码,根据规范,它应该几乎立即失败并抛出 ConcurrentModificationException;
- Java 7 确实会立即失败
- 但它(似乎)总是与 Java 6 一起工作(即它不会抛出承诺的异常)。
注意:Java 7 有时不会失败(比如 20 次中有 1 次)——我猜这与线程调度有关(即 2 个可运行对象没有交错)。
我错过了什么吗?为什么运行 Java 6 的版本不会抛出 ConcurrentModificationException?
实质上,有 2 个 Runnable 任务并行运行(使用倒计时闩锁使它们大致同时启动):
- 一个正在向地图添加项目
- 另一个是遍历地图,读取键并将它们放入数组中
然后主线程检查有多少键被添加到数组中。
Java 7 典型输出(迭代立即失败):
java.util.ConcurrentModificationException
MAX i = 0
Java 6 典型输出(整个迭代经过,数组包含所有添加的键):
最大 i = 99
使用的代码:
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;
}
}
}
}
注意:在 x86 机器上使用 JDK 7u11 和 JDK 6u38(64 位版本)。
【问题讨论】:
-
我会在
put循环中添加一个睡眠,以确保它们是并发的,并且在另一个开始之前没有运行完成。 -
@PeterLawrey 它确实使它失败得更快。但我不明白为什么我展示的代码在不同版本中表现不同。我很想知道其他观察者是否相同。
-
@assylias 在 Java 6 上是否会因额外的睡眠而失败?
-
我怀疑
await()在 Java 6 上唤醒线程的时间不像在 Java 7 中那样多。 -
文档规定:“请注意,不能保证迭代器的快速失败行为,因为一般来说,在存在不同步的并发修改的情况下,不可能做出任何硬保证。快速失败迭代器会尽最大努力抛出 ConcurrentModificationException。”
标签: java multithreading hashmap