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

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

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

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

Q&A

解決済

4回答

9133閲覧

Java ArrayList スレッドセーフ removeメソッド

flan

総合スコア146

Java

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

2グッド

1クリップ

投稿2017/03/31 04:21

編集2017/03/31 04:36

JavaのArrayLIstのスレッドセーフについて質問です。

Java SIlver 黒本より

ソース1

Java

1Arraylist<String> list = new ArrayList<>(); 2 3list.add("A"); 4list.add("B"); 5list.add("C"); 6 7for (String str : list){ 8 9 if("B".equals(str)){ 10 list.remove(str); 11}else{ 12System.out.println(str); 13} 14 15}

ソース2

Java

1Arraylist<String> list = new ArrayList<>(); 2 3list.add("A"); 4list.add("B"); 5list.add("C"); 6list.add("D"); 7list.add("E"); 8 9for (String str : list){ 10 11 if("C".equals(str)){ 12 list.remove(str); 13} 14 15} 16 17for (String str : list){ 18System.out.println(str); 19}

*クラスの宣言とmainメソッドは省略しました。


このソース1の実行した結果として
A
が出力。


ソース2は実行時に例外がスローされるとあります。
ConcurrentModificationException←これ

その理由として、ループで読みだしている最中にremoveメソッドを呼び出しているからとあります。
しかし、ソース1でもremoveメソッドを呼び出しているのにソース1では例外がスローされません。これはなぜでしょうか?

私なりの考察。

ソース1では拡張for文2回目のループでremoveメソッドが処理され3買い目のループは要素がなくなり2回目で終わり、残りの要素に影響されない。

ソース2では拡張for文3回目のループでremoveメソッドが処理され4回目ループの際の"E"がまだ残っているため要素数の順番に影響があるから?


よろしくお願いします。

stereo_code👍を押しています

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

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

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

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

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

guest

回答4

0

ベストアンサー

大まかに言うと考察はあってます。
要素数の順番ではなく、ループ中に要素数が変わることでエラーになります。

またソース1を実行した際も、
直感的にはAとCが出力されることが期待値になると思うのでエラーですよね。

ということで、普通ループ中に要素追加削除をするのはNGになっています。

stacktraceとclassファイル見ると多少詳しくと思うんですが、

java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901) at java.util.ArrayList$Itr.next(ArrayList.java:851)
Iterator arg2 = e.iterator(); while(arg2.hasNext()) { String str = (String)arg2.next(); if("B".equals(str)) { e.remove(str); } else { System.out.println(str); } }

ArrayList$Itr.checkForComodificationで要素数のチェック・エラー判定を行っているので、
ソース1の場合はremoveした後に次のhasNextでループを抜けるため、
nextが呼ばれずエラーになりません。

投稿2017/03/31 05:03

szk.

総合スコア1400

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

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

0

他の方の回答に似てますが微妙に違うコメントをしてみます。

ConcurrentModificationException(長いので以下CMEとよびます)が発生する・しないことの厳密な意味を理解しておくことは重要だと思います。

以下ArrayListのリファレンス:

通常、非同期の並行変更がある場合、確かな保証を行うことは不可能なので、イテレータのフェイルファストの動作を保証することはできません。フェイルファスト・イテレータは、ベスト・エフォート・ベースでConcurrentModificationExceptionをスローします。したがって、正確を期すためにこの例外に依存するプログラムを書くことは誤りです。イテレータのフェイルファストの動作はバグを検出するためにのみ使用すべきです。

つまりArrayListは多重処理による処理間違いを確実に検出することは狙っておらず、性能を劣化させない程度の手間をかけることで「うまくいけば例外を発することができることがある」となっているわけです。このためCMEが発生すれば「バグであることは明らか」ですが、CMEが発生しなかった場合は正しいかといえばその保証はないということになってしまいます。

コレクションに限らず一般にオブジェクトの状態を変更するスレッドが複数存在するなら**一貫性を保つに必要な処理範囲は他スレッドからの操作をガードする(排他する)**ことが必要です。ゆえにご質問のような処理をするなら例えば以下のようにしなければならない場合も多いでしょう。

java

1synchronized (同期用オブジェクト) { 2 for (String str : list){ 3 if("C".equals(str)){ 4 list.remove(str); 5 } 6}

補足:masaya_ohashiさんが挙げておられるIterator#removeは、こと多重処理についてはList#removeに比べ「より安全であるとはいえない」と思います。

masaya_ohashiさんご自身もIterator#removeが安全とは限らない点を「イテレータを自分で用意して、イテレータのremoveを使いましょう。 」という表現に込められているのだと思いました。ただ、読み手によっては「Iterator#removeを使えば安全なのかな?」とハヤトチリする可能性もあると思います。また実はそのような特別なIteratorを自分で定義するのは手間がかかる上に「それなりに処理コストがかかる」ものになるでしょう。ちなみにCollections.synchronizedListで同期リストを作ったとしてもそのiterator()メソッドで返されるイテレーターはhasNext(), next(), remove()などの操作を行う場合に利用者自身が(synchronized文を使って)同期した上で使わないといけないことにも注意すべきでしょう。こうしたことから、

複数のスレッドで同一のコレクションを並行して走査(かつ構造変更)をするというのは実現は可能ですが、個人的には余程の理由がない限りそういうことは避けて、走査処理全体をガードした方が簡単確実であることが多い気がします。

投稿2017/03/31 07:31

KSwordOfHaste

総合スコア18394

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

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

0

ConcurrentModificationExceptionが発生するかしないかは「時の運」がからんでいます。監視のタイミングの気まぐれによって起こるといっても過言ではありません。ちなみに、ソース1もソース2も、どちらも「スレッドセーフでないListの、イテレータ操作中のremove」という点においてConcurrentModificationExceptionが発生する可能性をはらんでいます。

Listからイテレータ操作中に安全に要素をremoveする場合、このような書き方が正しいです。

Java

1Iterator<String> itr = list.iterator(); 2while(itr.hasNext()){ 3 str = itr.next(); 4 if("C".equals(str)){ 5 itr.remove(); 6 } 7}

拡張for文において、「ConcurrentModificationExceptionの起こらない安全なremove方法」は存在しません。イテレータを自分で用意して、イテレータのremoveを使いましょう。

#追記
存在しないとは言いましたが、Collections.synchronizedListを使えば拡張for内で安全にremove可能な同期Listに置き換えることができます。ただし、あまり理解せずにこれに頼るのはよくないです。速度も遅くなりますし、デッドロックの発生の危険性もあります。
http://qiita.com/mountcedar/items/3a4d147d12e8b38cf6d0

投稿2017/03/31 04:58

編集2017/03/31 07:47
masaya_ohashi

総合スコア9206

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

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

0

投稿2017/04/01 17:05

swordone

総合スコア20651

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問