質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.37%
Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Q&A

解決済

3回答

39338閲覧

ConcurrentModificationExceptionが発生するパターンについて

k499778

総合スコア599

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

0グッド

10クリップ

投稿2015/09/28 01:03

現在Java Silverの勉強をしています。
そこでConcurrentModificationExceptionの問題がありました。
ConcurrentModificationExceptionは、ArrayListなどの非同期クラスのインスタンスが、別スレッドから変更されるとスローされるというのは知っているのですが、
ではなぜ次のような2パターンでは例外がスローされるのとされないので分かれるのでしょうか?

つまり、
下記のコードの前者ではConcurrentModificationExceptionが発生しないのに、
後者では発生するのはなぜでしょうか?

Java

1public class Main { 2 public static void main(String[] args) { 3 ArrayList<String>list=new ArrayList<String>(); 4 list.add("A"); 5 list.add("B"); 6 list.add("C"); 7 for (String str : list) { 8 if ("B".equals(str)) { 9 list.remove(str); 10 } 11 } 12 } 13}

Java

1public class Main { 2 public static void main(String[] args) { 3 ArrayList<String>list=new ArrayList<String>(); 4 list.add("A"); 5 list.add("B"); 6 list.add("C"); 7 for (String str : list) { 8 if ("C".equals(str)) { 9 list.remove(str); 10 } 11 } 12 } 13}

もちろん変更点はif文の条件が「B」と「C」とで変わっている点だとわかっているのですが、
それだとなぜ前者と後者で例外がスローされないのとされるので変わるかわかりません。
removeメソッドで要素を削除した場合、要素が繰り上がるのも知っています。
そもそもどこがどうマルチスレッドなのかがわかっていません。
言葉では「要素を取り出している処理」と「要素を削除している処理」の2つが並列処理なのかなとは思っているのですが、
コード上の動きや仕組みとしていまいちピンときていません。

いろいろと具体的に挙げてしまいましたが、
まずはなぜこの両者で例外が発生しないのとするので分かれるのか理解したいと思います。

もしわかる方がいればお願い致します。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

簡単に言えば:ConcurrentModificationException例外がスローされる/されないについては、何の保証もありません。「コレクション側で問題を検知できたときはできる限り早期にスローすること」という努力目標が設定されているだけです。

Java 7 SE ConcurrentModificationExceptionドキュメントより:

この例外は、オブジェクトが別のスレッドによって並行して更新されていないことを必ずしも示しているわけではありません。単一のスレッドが、オブジェクトの規約に違反する一連のメソッドを発行した場合、オブジェクトはこの例外をスローします。たとえば、フェイルファストイテレータを持つコレクションの繰り返し処理を行いながら、スレッドがコレクションを直接修正する場合、イテレータはこの例外をスローします。
通常、非同期の並行変更がある場合、確かな保証を行うことは不可能なので、フェイルファストの動作を保証することはできません。フェイルファストオペレーションは最善努力原則に基づき、ConcurrentModificationException をスローします。したがって、正確を期すためにこの例外に依存するプログラムを書くことは誤りです。ConcurrentModificationException は、バグを検出するためにのみ使用してください。

やたら難解な表現になっていますが、「フェイルファストオペレーションは最善努力原則に基づき、ConcurrentModificationException をスローします。」が”努力目標”に該当します。また「単一のスレッドが、オブジェクトの規約に違反する一連のメソッドを発行した場合、オブジェクトはこの例外をスローします。」とある通り、その名前(Concurrent)に反して、シングルスレッドでもスローされることがあります。

投稿2015/09/28 05:14

yohhoy

総合スコア6191

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

k499778

2015/09/28 05:24

yohhoyさん いつもありがとうございます。 仕様はそういうことを言っているのですね。いつも読んでみるのですが、なかなか理解できなくて。 仕様では努力目標だけ設定されていてマルチでもシングルでもConcurrentModificationExceptionはスローされることがあるんですね。 勉強になります。ありがとうございました。
guest

0

ベストアンサー

内部のコードを読むと,ArrayListとIteratorは内部で「変更された回数」を保持しています.
そしてArrayListのiterator()メソッドで生成されるIteratorはforで回す際に使うnext()メソッドなどを起動した際に,変更された回数にミスマッチがないかチェックし,ミスマッチしていればConcurrentModificationExceptionを投げる仕組みになっています.
また,ArrayListのIteratorのhasNext()メソッドは,「現在位置とリストのサイズが一致していない場合にtrueを返す」というコードになっています.これを踏まえて前者・後者のコードを読み解いていきます.

前者:
0. "B"が取り出される直前,Iteratorの現在位置は"B"の位置である"1"
0. next()によって"B"が取り出され,Iteratorの現在位置は1加算され"2",remove()でリストのサイズは2になる
0. 次のループでhasNext()はサイズ=現在位置によりfalseを返す.ループを抜けるため変更回数チェックがされず例外は発生しない

後者:
0. "C"が取り出される直前,Iteratorの現在位置は"C"の位置である"2"
0. next()によって"C"が取り出され,Iteratorの現在位置は1加算され"3",remove()でリストのサイズは2になる
0. hasNext()はサイズ≠位置なのでtrue.ループ継続する
0. 次のnext()でリスト側の変更回数がremove()によって1回多いことが判明,例外発生
こういうことになります.

追記・補足
ArrayListのJavadocに次のような記述があります.

このクラスの iterator および listIterator メソッドによって返されるイテレータは、フェイルファストです。イテレータの作成後に、イテレータ自体の remove または add メソッド以外の方法でリストが構造的に変更されると、イテレータは ConcurrentModificationException をスローします。このように、並行して変更が行われると、イテレータは、将来の予測できない時点において予測できない動作が発生する危険を回避するために、ただちにかつ手際よく例外をスローします。
通常、非同期の並行変更がある場合、確かな保証を行うことは不可能なので、イテレータのフェイルファストの動作を保証することはできません。フェイルファストイテレータは最善努力原則に基づき、ConcurrentModificationException をスローします。したがって、正確を期すためにこの例外に依存するプログラムを書くことは誤りです。「イテレータのフェイルファストの動作はバグを検出するためにのみ使用すべきです」。

投稿2015/09/28 05:08

編集2015/09/29 09:35
swordone

総合スコア20669

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

k499778

2015/09/28 05:17

swordoneさん いつもありがとうございます。 そういうことなんですね。 拡張for文は暗黙的にIteratorを使用するため、実際はそういった動きにより例外が発生したりしなかったりしていたのですね。 理解できました。ありがとうございます。 ちなみにもしお時間あればで結構なのですが、 内部のコードというのは何なのでしょうか?実際にどういう動きをしているか。という認識で合っているでしょうか? またそれを目で見て確かめる方法はありますか? エクリプスでデバッグし、F6で実際にコードを1行1行追っていったのですが、そこまでのことはわかりませんでした。F5でステップインするのかな? もしよろしければお答えいただけたらと思います。
swordone

2015/09/28 06:15 編集

JDKに添付されているソースコードのことです. 例外発生時にスタックトレースがコンソールに表示されると思います. 私も実際に例外が発生する方のコードをEclipseで実行しました. すると次のようなログが表示されます. Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901) at java.util.ArrayList$Itr.next(ArrayList.java:851) at teratail.question16901.Main.main(Main.java:12) この括弧内はリンクになっており,クリックするとプログラムの該当行にジャンプします. 違うクラスの場合はそのクラスのソースコードが別タブで開きます. ステップインでもその行を通って行くことが確認できるかと思います. ちなみに,このログの2行目にあるcheckForComodificationが,上記回答で言った「変更回数にミスマッチがないかチェックする」メソッドです.
k499778

2015/09/28 07:21

swordoneさん 追加回答ありがとうございます。 おっしゃる通りですね。勉強になりました。自分でも色々と試してみて理解を深めたいと思います。ありがとうございました。
swordone

2015/09/28 14:02

ちなみに,Eclipse上のコードのクラス名やメソッド名にマウスオーバーすると, そのクラスまたはメソッドの説明の小さな窓が表示されます. その窓をクリックまたはしばらくマウスオーバーして枠が出てきたところで 左側のマークをクリックするとそのクラス/メソッドに飛べます.
guest

0

Java 7の解説によれば、

この例外は、オブジェクトが「別の」スレッドによって並行して更新されていないことを必ずしも示しているわけではありません。単一のスレッドが、オブジェクトの規約に違反する一連のメソッドを発行した場合、オブジェクトはこの例外をスローします。

とあります。別に、スレッド実行と関係なくこの例外が発生することもあるようです。

おそらく、削除したイテレーターから次のステップへ進もうとして例外になるので、最後の時だけは次に進むことがなく例外にならなかった、ということでしょう。

投稿2015/09/28 01:18

maisumakun

総合スコア145930

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

k499778

2015/09/28 03:57 編集

maisumakunさん 回答ありがとうございます。 そうなんですね。別スレッドから変更された場合の例外だと思っていたので、 なんだかつじつまが合わなかったのですが、そういう理由だと知れてよかったです。 拡張for文に関しては一見イテレーターを使用していないように見えますが、 暗黙的にイテレーターを使用するfor文であるため、イテレーターを使用しているということですよね。まず前提の話になってしまっていますが。実はイテレーターにあまり詳しくないので。少し調べて、「コレクションに格納されたすべての要素に対して処理を実施する」ものでIteratorインターフェイスがあり、nextメソッドやhasNextメソッドで要素の検証をすることができると知りました。 少し逸れましたが、例外が発生している理由が知れてよかったです。ありがとうございました。
k499778

2015/09/28 02:15 編集

あ、すいません。 少し考えたのですが。 真ん中の要素(B)を削除する前者は例外が起きず、 最後の要素(C)を削除する後者は例外が発生しました。 maisumakunさんの >最後の時だけは次に進むことがなく例外にならなかった という部分にそぐわない気がするのですが。最後を削除したときに例外になったので。 もしお時間ございましたらお答えいただけると嬉しいです。
maisumakun

2015/09/28 02:58

すみません、読み間違えていました。例外の発生原因の部分については勘違いでのコメントなので無視してください。
k499778

2015/09/28 03:55

maisumakunさん 返答ありがとうございます。 そうでしたか。ただ回答いただけたこと感謝しています。ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.37%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問