[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[jfriends-ml 1631] volatile のはなし Re: Java スレッドプログラ ミングを読む会第 6 回の議事録



こんばんは武川です。

From: Takayuki Sato <takayuki@xxxxxxxxx>
Subject: [jfriends-ml 1629] Java スレッドプログラミングを読む会第 6 回の議事録
Date: Wed, 25 Apr 2001 21:49:49 +0900

> 3.2.6.5 実装
> 
> SpinLockクラスのreleaseメソッドにはなぜsynchronizedを付ける必要がある
> のだろうか(busyはvolatileのboolean型なのに)?
> →synchronizedによって実行順序が保証されるから?一つのスレッド内であ
> れば意味的におかしくなることはないが複数のときはおかしくなることがあ
> るかもしれない?
> →volativeがJVMで正しく実装されていないかもしれないことまでを考慮して
> いるから?
> 
> この件に関しては、武川さんが訳者の松野さんに質問中です。
 
ということで、質問してみましたところ、非常に丁寧なメールを
頂き納得できました。以下松野さんから頂いたメールを転送します。
ヘッダとシグネチャについては、不必要な部分は削っています。
また、4/26にもらったのにMLに投稿するのが遅れたのは、単に
僕がさぼっていただけです。ごめんなさい。

-----------------------ここから------------------------------

Subject: RE: [Javaスレッドプログラミング] volatileとsyncronizedについての質問
From: "Matsuno, Ryouzo" <Ryouzo.Matsuno@xxxxxxxxxxxxx>
To: "'TAKEKAWA Tsutomu'" <takekawa@xxxxxxxxxxxxxx>
Date: Thu, 26 Apr 2001 20:28:01 +0900
X-Mailer: Internet Mail Service (5.5.2652.78)

武川様、

読書会まで開いていただいて、訳者冥利に尽きる気持ちです。

さて、お問い合わせの件ですが、私もこの分野の専門家というわけではないので、
あくまで私の意見というレベルでお答えします。

まず、Volatile変数は、あくまで変数に対する個別のアクセスごとにメインメモリ
との同期をとることを指定するので、ここに記述されているように「メモリ同期が
ロックを解放したときに発生することを保証」するためには、ロックを利用する
しかないと思います。

例の場合に限定すると、Releaseメソッド内には1つのVolatile変数に対する
1つのアクセスしか存在しないので、そのような問題はありません。
しかし、Volatile変数に対する並列アクセスのルールでは、変数の値をメインメモリ
からRead/Writeする処理、スレッドのワーキングメモリにLoad/Storeする処理、
ワーキングメモリの値をUse/Assignする処理に分けて、処理の原子性はRead、
Load、Useといった各処理にしか与えません。Volatileを指定することにより、
Useに対する同じスレッド内の直前の処理はLoad、Storeに対する同じスレッド内の
直前の処理はAssignに限定されるので、ワーキングメモリに対する操作とメインメモ
リ
の間で、非常に短いタイミングで同期が取れます。しかし、他のスレッドの処理との
間に完全な排他性は提供できないことになると思われます。

これは2.2.7.4で出てくる「完全に同期化されたクラスとほとんど同じ効果」という
説明と矛盾するようですが、「ほとんど」が曲者でしょう。

こうなると、Volatile変数は並列処理では意味がなくなってしまいそうですが、
1つのスレッド内での複数のVolatile変数へのアクセスは、外部からでも同じ
順序で観察できるというルールが規定されています。1つのスレッドでだけ
変更が行われ、他のスレッドは読み込むだけの場合には、意味がありそうです。

この例の場合には、変更部分はSynchronizedで囲まれていますが、最初のテストは
ロックを確保せずに行います。最初のテストで条件をできるだけ見逃さないためには
Volatileが有効かと思います。

なお、Volatileを含めたマルチスレッド環境でのメモリアクセスの仕様は、
Java Language SpecificationかJVM Specificationに規定されています。
この部分はかなり複雑で議論も多いようです。

-----Original Message-----
From: TAKEKAWA Tsutomu [mailto:takekawa@xxxxxxxxxxxxxx]
Sent: Thursday, April 26, 2001 1:53 AM
To: Matsuno, Ryouzo
Subject: [Javaスレッドプログラミング] volatileとsyncronizedについての質
問



松野様

はじめまして、武川と申します。

現在、有志で「Javaスレッドプログラミング」の読書会を
行なっております。非常に難しいのですが、面白くに読ませて
もらっています。このような訳本を出してもらって感謝しています。
ありがとうございます。
(http://www.t3-jpn.com/bof/ でどのような会か見ることが出来ます。)

さて、本題なのですが、議論したのですがどうしても理解できない
部分がありました。英語版のWebページなども見たのですが、
この部分について議論したページを見つけることが出来ませんでした。
本来ならば、直接著者に聞くべきなのかもしれませんが、
もし、お時間が許すようでしたら、お答えを頂けないでしょうか?

以下、疑問について説明します。

まず、初版第1版のP.225  3.2.6.5 実装の2段落目を引用します。

「この例では、releaseメソッドはメモリ同期がロックを解放したときに
  発生することを保証するために、synchronizedを指定されています。」

という記述があります。ソースコードを引用すると、

class SpinLock {  //thisを利用する必要性を避ける
  private volatile boolean busy = false;
  synchronized void release() { busy = false; }
....

となっています。

疑問点は、
「フィールドbusyは、volatileの属性を持っていて、なおかつboolean
  型なのだから、書きこみ時にメモリ同期が行なわれるはずなので、
  synchronizedする必要性はないのではないか?」
というものです。

これに対して、以下のような幾つかの意見が出ました。

1. synchronizedによって実行順序が保証される。一つのスレッド内であれば
意味的におかしくなることはないが複数のときはおかしくなることがある
可能性があるのではないか。

2. volatileが有効でないJVMに対して保険をかけたのではないか。

自分の考えとしては、1については、busy = falseの文については、
アトミックな動作であると思われるので、順序がおかしくなるような
ことはないと思っています。また、2については、仕様どおりでない
と言いだすとキリがないので、これも理由としてはあまり意味
がないのではないかと考えています。

また、syncronizedを付ける必要があるならば、2.2.7.2の2 (P.109)
で説明しているvolatileの説明と矛盾してしまうようにも思えます。

現時点での理解は上記の通りなのですが、何か、見落としている点
などがあれば、指摘して頂けると非常にありがたいです。
また、返事については読書会のメーリングリストに送り、メンバ
で情報共有する予定です。

以上、よろしくお願い致します。

-----------------------ここまで------------------------------

参考までに
http://www.y-adagio.com/public/standards/tr_javalang/17.doc.htm#28330
から17.7節を引用しておくと以下のように記述してあります。

-----------------------ここから------------------------------

17.7 Volatile宣言された変数の規則
変数が volatile 宣言されていれば,付加的な制約を各スレッドの動作に適用
する。T をスレッド,V 及び W を volatile 宣言された変数とする。

T による V への 使用 は,T による V への以前の動作が ロード の場合に限
り許され,T による V への ロード 動作は,T による V への次の動作が 使
用 の場合に限り許される。使用 動作は,ロード に対応する 読取り と“関
連している”という。

T による V への 記憶 動作は,T による V への以前の動作が 代入 の場合に
限り許され,T による V への 代入 動作は,T による V への次の動作が 記
憶 の場合に限り許される。代入 動作は,記憶 に対応する 書込み と“関連
している”という。

動作A をスレッド T による変数 V への 使用 又は 代入 とし,動作 F を A
と関連した ロード 又は 記憶 とし,動作 P を F に対応した V の 読取り
又は 書込み とする。同様に,動作 B をスレッド T による変数 W の 使用
又は 代入 とし,動作 G を B と関連した ロード 又は 記憶 とし,動作 Q
を G に対応した V の 読取り 又は 書込み とする。A が B に先行する場合,
P は, Q に先行しなければならない。(つまり,スレッドの振舞いによる,
volatile 宣言された変数のマスタコピーに対する動作は,主メモリによって,
そのスレッドが要求した順序とまったく同じ順序で実行される。)

-----------------------ここまで------------------------------

これを読むと、定義されているのは、単一のスレッド時の動作順序
であって、複数スレッドの場合ではないので、複数スレッドの場合に
同期を取りたい場合はsynchronizedを使うのが吉ということになるので
しょうか?

うーん。難しいですね。

ではでは。