フェイルセーフイテレータとフェイルファストイテレータ

1前書き

この記事では、Fail-FastとFail-Safe Iterators の概念を紹介します。

Fail-Fastシステムは、できるだけ早く操作を中止し、ただちに障害を発見して操作全体を停止します。

一方、** Fail-Safeシステムは、失敗した場合に操作を中止しません。そのようなシステムは、失敗をできるだけ回避するように試みます。

2フェイルファースト イテレーター

基になるコレクションが変更されたときにJavaのフェイルファストイテレータはうまくいかない。

Collections modCount と呼ばれる内部カウンタを管理します。アイテムが Collection に追加または削除されるたびに、このカウンタは増加します。

繰り返すと、 next()を呼び出すたびに、 modCount の現在の値が初期値と比較されます。不一致があると、 ConcurrentModificationException__がスローされ、操作全体が中止されます。

ArrayList HashMap などの java.util package Collections のデフォルトの反復子はFail-Fastです。

ArrayList<Integer> numbers =//...

Iterator<Integer> iterator = numbers.iterator();
while (iterator.hasNext()) {
    Integer number = iterator.next();
    numbers.add(50);
}

上記のコードスニペットでは、変更が実行された後の次の繰り返しサイクルの開始時に ConcurrentModificationException がスローされます。

Fail-Fast動作はすべてのシナリオで起こることが保証されているわけではありません。同時変更の場合に動作を予測することは不可能だからです。

  • これらのイテレータは、ベストエフォートベースで ConcurrentModificationException をスローします。

Collection の繰り返し中に Iterator s remove() メソッドを使用して項目が削除された場合、これは完全に安全で、例外をスローすることはありません

ただし、 Collection `s remove() メソッドを使用して要素を削除すると、例外がスローされます。

ArrayList<Integer> numbers =//...

Iterator<Integer> iterator = numbers.iterator();
while (iterator.hasNext()) {
    if (iterator.next() == 30) {
        iterator.remove();//ok!
    }
}

iterator = numbers.iterator();
while (iterator.hasNext()) {
    if (iterator.next() == 40) {
        numbers.remove(2);//exception
    }
}

3フェイルセーフイテレータ

Fail-Safeイテレータは例外処理の不便さよりも失敗の欠如を支持します。

それらの反復子は実際の Collection のクローンを作成し、それを反復処理します。イテレータの作成後に何らかの変更が行われても、コピーはそのまま残ります。したがって、これらの Iterators は、たとえ変更されたとしても Collection をループし続けます。

ただし、本当にFail-Safeイテレータのようなものはないことを覚えておくことは重要です。正しい用語は弱一貫性です。

つまり、 Collection が反復されている間に変更された場合、 Iterator が見たものは弱く保証されています** 。この動作は Collections ごとに異なる場合があり、それぞれの Collection__についてJavadocに記載されています。

ただし、フェイルセーフ イテレータ にはいくつかの欠点があります。欠点の1つは、** イテレータは、実際の コレクションではなくクローンを処理しているため、__コレクションから更新されたデータを返すことが保証されていないことです。

もう1つの欠点は、時間とメモリの両方に関して、 Collection のコピーを作成するオーバーヘッドです。

ConcurrentHashMap CopyOnWriteArrayList などの java.util.concurrent パッケージの Collections 上の Iterators は、本質的にフェールセーフです。

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();

map.put("First", 10);
map.put("Second", 20);
map.put("Third", 30);
map.put("Fourth", 40);

Iterator<String> iterator = map.keySet().iterator();

while (iterator.hasNext()) {
    String key = iterator.next();
    map.put("Fifth", 50);
}

上記のコードスニペットでは、Fail-Safe Iterator を使用しています。したがって、繰り返しの間に Collection に新しい要素が追加されても、例外はスローされません。

ConcurrentHashMap のデフォルトのイテレータ __は、一貫性があまりありません。つまり、この Iterator は同時変更を許容でき、 Iterator の構築時に存在した要素をトラバースし、 Iterator の構築後に Collection__の変更を反映することは保証されません(ただし保証はされません)。

したがって、上記のコードスニペットでは、反復は5回ループします。つまり、** Collection に新しく追加された要素が検出されます。

4結論

このチュートリアルでは、Fail-SafeとFail-Fast Iterators が何を意味するのか、そしてこれらがJavaでどのように実装されるのかを説明しました。

この記事に記載されている完全なコードはhttps://github.com/eugenp/tutorials/tree/master/core-java-collections[over on GitHub]から入手できます。