HashMap の javadocには次のように記載されています。
イテレータの作成後に、イテレータ自体の remove メソッド以外の方法でマップが構造的に変更された場合、イテレータは ConcurrentModificationException をスローします。
仕様に基づいて、ほぼ即座に失敗し、ConcurrentModificationException をスローするサンプル コードを作成しました。
- Java 7 では予想どおりすぐに失敗します
- しかし、Java 6 では常に動作するようです (つまり、約束された例外をスローしません)。
注: Java 7 では失敗しない場合があります (20 回のうち 1 回など) - スレッドのスケジューリングに関係していると思います (つまり、2 つのランナブルがインターリーブされていません)。
何か不足していますか?Java 6 で実行されるバージョンが ConcurrentModificationException をスローしないのはなぜですか?
実際には、2 つの Runnable タスクが並行して実行されています (ほぼ同時に開始するためにカウントダウンラッチが使用されます)。
- 1つはマップにアイテムを追加することです
- もう1つはマップを反復処理し、キーを読み取って配列に入れます
次に、メイン スレッドは、配列に追加されたキーの数をチェックします。
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 ビット バージョン) を使用します。