水曜日 2 21, 2007
水曜日 2 21, 2007
お待たせしました、少し難しめの問題でしたがいかがだったでしょうか?今回の問題は先月の「今月のJava Hot Topicセミナー(1月号)」で山口さんによる「コンカレンシー・ユーティリティのすすめ」をご覧頂くとその問題点と解決策がおわかりいただけると思います。 ではまず問題の復習から。 このプログラムのバグを取り除いてください次のプログラムはupdate(int)メソッドによって今までの最大値と比較して、与えられた数が今までで最大であれば、最大値を更新するようなプログ ラムです。しかし、このプログラムはある状況下で期待通り動作しません。そのバグの内容と、バグを修正したプログラムを考えてください。
public class MaxValue {
回答この問題の解答は幾つかパターンが考えられますが、今回は上記でご紹介したセミナーの資料「コンカレンシー・ユーティリティのすすめ (以下、「資料」と言うときはこの資料のことです)」にそって回答をご紹介します。 このプログラムは一見難の問題も無く動作しそうですが、実はマルチスレッド環境では正しく動作しないことがあります。これはJavaのメモリモデルと深く関わる問題です。 このような場合に、CPU 1でこれから処理を仕様としているときに、CPU 2で今まさに更新されたデータを利用するとするとどうなるでしょうか。このとき、CPU 1はデータが更新された事を知らないので、古い情報を使ってしまう場合があります。 これに対処するにはJ2SE 5.0で厳密に定義されたメモリモデルのルールに従ったプログラミングをする必要があります。 これをふまえてプログラムを修正すると次の2パターンのようになります。 パターン1ではすべてのメソッドを同期化することで安全に呼び出しを行っています。一方、パターン2ではvolatileをmaxを宣言する際に装飾する事で必ずこの変数を読み出す前にキャッシュをクリアし、書き出す時にキャッシュをフラッシュするようにしています。パターン2の方は読み出しについては同期化されないためパフォーマンスのオーバーヘッドが少なくてすみます。 これで十分な場合もほとんどですが、さらにパフォーマンスのボトルネックを減らす方法を考えます。近代的なマルチCPUシステムでは、CPUあるいはそのシステムによってメモリの整合性を保証しつつ値を更新するような命令がサポートされています。その典型的なものがCAS (Compare And Swap)命令です。CAS命令は
のように3つの引数を持つ命令で、v == aなら vに bを代入するという命令です。この操作はCPUあるいはシステムによって整合性が保証されるため、この操作を行うためにロックを取得したり、ロック待ちになるということが無く、(アプリケーションやOSから見て) 処理の流れを止める事がありません。このCAS命令は J2SE 5.0から導入された java.util.concurrent パッケージのユーティリティを使う事で Javaからも利用可能になりました。この concurrentパッケージを使った回答例を見てみましょう。 まずCAS命令を利用するためにjava.util.concurrent.AtomicIntegerクラスを使って最大値 maxを保持しています。そして、updateメソッドでは compareAndSwap (つまり CAS)を使って値を更新しているのです。この compareAndSwapは max == oldの場合には実行が成功して戻り値が trueになりますが、max != oldつまり、別のスレッドによって値が更新された場合には戻り値が falseとなって再度比較を行うようにしています。 このようにしてCPUやマルチプロセッサ/マルチコアシステムで提供されている強力な命令を利用したプログラムに生まれ変わりました。皆さんもこれを参考にしてスループット重視の安全なプログラムを java.util.concurrentパッケージを使って実装してみてください。 投稿されたコメント:
|
|