Javaエバンジェリストがお届けするホットな話題
5分でわかる今週のJava ホットトピック
水曜日 2 21, 2007
先週のJavaクイズ(2) 回答編:マルチスレッド環境にて

お待たせしました、少し難しめの問題でしたがいかがだったでしょうか?今回の問題は先月の「今月のJava Hot Topicセミナー(1月号)」で山口さんによる「コンカレンシー・ユーティリティのすすめ」をご覧頂くとその問題点と解決策がおわかりいただけると思います。

ではまず問題の復習から。

このプログラムのバグを取り除いてください

 次のプログラムはupdate(int)メソッドによって今までの最大値と比較して、与えられた数が今までで最大であれば、最大値を更新するようなプログ ラムです。しかし、このプログラムはある状況下で期待通り動作しません。そのバグの内容と、バグを修正したプログラムを考えてください。

 

public class MaxValue { 
private int max;

public void update(int value) {
if (max < value)
max = value;
}

public int get() {
return max;
}
}

 

回答

この問題の解答は幾つかパターンが考えられますが、今回は上記でご紹介したセミナーの資料「コンカレンシー・ユーティリティのすすめ (以下、「資料」と言うときはこの資料のことです)」にそって回答をご紹介します。

このプログラムは一見難の問題も無く動作しそうですが、実はマルチスレッド環境では正しく動作しないことがあります。これはJavaのメモリモデルと深く関わる問題です。
資料の p8〜p9ご覧ください。近代的なコンピュータはデータをCPUのレジスタとメモリだけでなく、処理を高速化させるために様々な部分にキャッシュをするようになっています。それもCPUだけでも二次キャッシュ、三次キャッシュというように多段の構造になっている事も珍しい事ではありません。

20070131_hottopic2_concurrency_p8

このような場合に、CPU 1でこれから処理を仕様としているときに、CPU 2で今まさに更新されたデータを利用するとするとどうなるでしょうか。このとき、CPU 1はデータが更新された事を知らないので、古い情報を使ってしまう場合があります。

これに対処するにはJ2SE 5.0で厳密に定義されたメモリモデルのルールに従ったプログラミングをする必要があります。

20070131_hottopic2_concurrency_p9

これをふまえてプログラムを修正すると次の2パターンのようになります。

パターン1
20070131_hottopic2_concurrency_p12
パターン2
20070131_hottopic2_concurrency_p13

パターン1ではすべてのメソッドを同期化することで安全に呼び出しを行っています。一方、パターン2ではvolatileをmaxを宣言する際に装飾する事で必ずこの変数を読み出す前にキャッシュをクリアし、書き出す時にキャッシュをフラッシュするようにしています。パターン2の方は読み出しについては同期化されないためパフォーマンスのオーバーヘッドが少なくてすみます。

これで十分な場合もほとんどですが、さらにパフォーマンスのボトルネックを減らす方法を考えます。近代的なマルチCPUシステムでは、CPUあるいはそのシステムによってメモリの整合性を保証しつつ値を更新するような命令がサポートされています。その典型的なものがCAS (Compare And Swap)命令です。CAS命令は

CAS v, a, b

のように3つの引数を持つ命令で、v == aなら vに bを代入するという命令です。この操作はCPUあるいはシステムによって整合性が保証されるため、この操作を行うためにロックを取得したり、ロック待ちになるということが無く、(アプリケーションやOSから見て) 処理の流れを止める事がありません。このCAS命令は J2SE 5.0から導入された java.util.concurrent パッケージのユーティリティを使う事で Javaからも利用可能になりました。この concurrentパッケージを使った回答例を見てみましょう。

パターン3
20070131_hottopic2_concurrency_p20

まずCAS命令を利用するためにjava.util.concurrent.AtomicIntegerクラスを使って最大値 maxを保持しています。そして、updateメソッドでは compareAndSwap (つまり CAS)を使って値を更新しているのです。この compareAndSwapは max == oldの場合には実行が成功して戻り値が trueになりますが、max != oldつまり、別のスレッドによって値が更新された場合には戻り値が falseとなって再度比較を行うようにしています。

このようにしてCPUやマルチプロセッサ/マルチコアシステムで提供されている強力な命令を利用したプログラムに生まれ変わりました。皆さんもこれを参考にしてスループット重視の安全なプログラムを java.util.concurrentパッケージを使って実装してみてください。


投稿されたコメント:

コメント
コメントは無効になっています。
過去の記事
« 12月 2009
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
  
       
今日
Click me to subscribeこのブログを購読する(RSS)
検索

リンク
 

Today's Page Hits: 42