人力検索はてな
モバイル版を表示しています。PC版はこちら
i-mobile

Javaの同期化の質問です。HashMapは同期化されないことが明言されていたので、Hashtableを使って「これで安全」と思っていたところ、Enumeratorを使ってループをしているコードで
ConcurrentModificationExceptionが送出されました。for文の前後で明示的にロック・アンロックすべきなのでしょうか?またなにか慣用句的なコードはありますか?

●質問者: westfish
●カテゴリ:コンピュータ
✍キーワード:Java コード ループ ロック 前後
○ 状態 :終了
└ 回答数 : 4/4件

▽最新の回答へ

1 ● quintia
●23ポイント

Hashtable のすべての「コレクションビューメソッド」によって返される Collection の iterator メソッドおよび listIterator メソッドによって返される Iterator は、「フェイルファスト」です。Iterator の作成後に、Iterator 自体の remove メソッドまたは add メソッド以外の方法で Hashtable が構造的に変更されると、Iterator は ConcurrentModificationException をスローします。

http://java.sun.com/j2se/1.3/ja/docs/ja/api/java/util/Hashtable....

iterator メソッドや listIterator メソッド で返されるイテレータを通しての操作に関しては、同期は保証されず「フェイルファスト」つまり「データが操作された可能性があるならば即座に例外を発生して処理を中断する」仕様です。


http://java.sun.com/j2se/1.3/ja/docs/ja/api/java/util/Collection...

java.util.Collections の static メソッド synchronizedMap を使うのが常套です。

ただし、Hashtable での iterator メソッドと同じ問題が存在しています。

返された Map への直接のアクセスは同期されますが、entrySet() keySet() values() メソッドを使う場合は、補足説明されている通りの同期処理を書く必要があります。

synchronizedMap メソッドの説明とサンプルコードをご覧ください。

◎質問者からの返答

なるほど、「Hashtable自体はスレッドセーフだが、Hashtable.values()などの返り値は必ずしもスレッドセーフではない」ということですね。


2 ● bellbind
●23ポイント

使用状況がわからないので明確な答えは出せませんが、Java5であればjava.util.ConcurrentHashMapである程度の典型的状況は解決可能だと思います。

◎質問者からの返答

使用状況をおおざっぱに説明しますと、あるHashtable Xを毎秒数十回読み出して画面にグラフィック描画をするスレッドと、XML-RPCでクエリーを受けてXに要素を追加するスレッドがあるような状況です。リンク先を読んでみます。


3 ● quintia
●22ポイント

1.の回答者です。

コメントの

「Hashtable自体はスレッドセーフだが、Hashtable.values()などの返り値は必ずしもスレッドセーフではない」ということですね。

「スレッドセーフではない」という表現には違和感があります。


あるスレッドが clear メソッドを実行中に、別のスレッドが remove メソッドを実行したとします。


「スレッドセーフでない」というのは、例えば、remove した要素を clear がもう一度取り除こうとしておかしくなる、という様な状況を意味するでしょう。


Hashtable が持つ add, clear, contains, remove, size, (etc...) のメソッド群はスレッドセーフです。

「スレッドセーフである」というのは、clear メソッドと remove メソッドの両方が同一時点で実行されている、という状況が起らないということです。


そして、Hashtable#values() で取り出したコレクションビュー(Collection のインスタンス) が持つ remove, size などのメソッドも、取り出し元の Hashtable のメソッド群に対してスレッドセーフです

Hashtable の clear メソッドと、Hashtable#values で取り出したコレクションビューの removeメソッドは同期されます。Hashtable#clear メソッドと Collections#remove メソッドが同一時点で実行されている、という様なことはありません。(JDK1.4 の Hashtable.java を読むと、Hashtable のインスタンス自身で同期されています)


Hashtable#values でコレクションビューを取り出した後で、Hashtable の add, clear, remove などの要素を操作してしまうメソッドを実行すると、取り出し済のコレクションビューも影響を受けます。

コレクションビューの側が、すでに変化してしまった状態を取り出したりしないように、例外を発生させて状態が変化したことを知らせる様になっているのです。それが「フェイル・ファスト」の概念です。

逆に言うと、contains や size などの要素を操作しないメソッドは問題ない――つまり同期されているし、また、コレクションビューの側の remove なども同期されます。(ただし2つのコレクションビューを取り出している時には、片側で remove するともう一方で ConcurrentModificationException が発生するわけですが)


「フェイル・ファスト」なコレクションビューではなく、「一貫性のある」コレクションビューを提供するのが、2.の回答の ConcurrentHashMap ですね。ただその代償として、

これらが ConcurrentModificationException をスローすることはありませんが、一度に 1 つのスレッドのみが反復子を使用するように設計されています。

http://java.sun.com/j2se/1.5.0/ja/docs/ja/api/java/util/concurre...

ということになるわけです。

◎質問者からの返答

なるほど、スレッドセーフという言葉の意味を勘違いしていたようです。スレッドセーフにするための方法として、イテレータの使用中は追加や削除ができないようにsynchronizedブロックで囲む方法と、オブジェクトがおかしくなる前に例外を投げて止めてしまう方法とがあるわけですね。

結局のところ、コレクションフレームワークを使った解決方法では、コレクションのインスタンスがイテレーションの終了を知ることができない為に、「イテレーションしている間は追加削除をさせない」ということが困難なわけですね。自分でイテレーションの部分と追加部分をsyncronizedで囲うのが一番適切な対処法であるように思えてきました。


4 ● bellbind
●22ポイント
あるHashtable Xを毎秒数十回読み出して画面にグラフィック描画をするスレッドと、XML-RPCでクエリーを受けてXに要素を追加するスレッドがあるような状況です。

まず、書き込みのほうですが、追加するスレッドが何個もあるようであれば、ConcurrentHashMapを使うとHashtableより効率はよくなります。それは先に紹介したdeveloperWorksのほうのリンクにも言及があります。


また描画スレッドなどでコレクションデータ全体を走査する場合はそのコレクションを直接走査するのではなく、参照するスレッド(ループ)の開始時などで、一度ConcurrentHashMapをHashMapにコピー(たとえば、Map viewMap = new HashMap(storeMap))して、それを使うというのがセオリーでしょうか。


描画や計算などでは、ある時点でのデータコレクションのスナップショットを取ってそれを処理に使うわけです。これは普通のHashMapをロックして使う場合や、Hashtableを使う場合、それ以外のデータ構造であっても同様です。

◎質問者からの返答

グラフィック描画は秒間数十回行われています。そのたびにコレクションをコピーするのはあまり現実的なソリューションではないと思います。

やはり描画や計算の開始時点でロックをかけるのがよさそうです。

関連質問


●質問をもっと探す●



0.人力検索はてなトップ
8.このページを友達に紹介
9.このページの先頭へ
対応機種一覧
お問い合わせ
ヘルプ/お知らせ
ログイン
無料ユーザー登録
はてなトップ