やっぱり、Java はよくわからん。というのも、次のような記事を読んだからだ。
Double-checked locking: Clever, but broken
ナナメ読みしかしてないので、私の理解が間違っている可能性はあるが、 Java Memory Model が規定するところによれば、メモリアクセスの 順番を入れ替えたり最適化したりすることは、シリアルセマンティクス、 つまり、シングルスレッドな環境においてうまく動作するものであれば、 何でも許容される、ということらしい。
これはどういうことかというと、たとえば、
foo = aVariable; bar = aVariable;
というコードがあったなら、コンパイラは、bar への代入については、
aVariable へのアクセスをサボって、foo の値
(あるいは、レジスタにキャッシュされている値) を使って構わない、ということである。
このこと自体は、言ってみれば、当たり前のことで、とくに問題となるとは 思えないであろう。ところが、前稿 で挙げた、 「二重チェック」の Java 版である次のコードがうまく働かない可能性がある、 というのは、十分、驚くに値する話だ。
class SomeClass { private Resource resource = null; public Resource getResource() { if (resource == null) { synchronized { if (resource == null) resource = new Resource(); } } return resource; } }
私は Java のことをよく知らないので、なぜこれがうまく働かないのか、 理解に苦しむのだが、推測するに、
synchronized という
キーワードがあるにもかかわらず、一連のコードは、「シリアルセマンティクス」
のもとで動いていると想定されてしまうresouce == null での resouce は、
1回目のそれをキャッシュした値が使われてしまうことがありうるsynchronized
でブロックされている間に別のスレッドが resouce
を生成していたかもしれないのに
ということだろうか。恐るべきことに、この不具合は、volatile
修飾子を付けても直らないのだと、件の記事は言う。
幸いにして、と言うべきか、C++ においては、
マルチスレッドなどという高級な概念は完全に仕様の外の話なので、
volatile はちゃんと機能する。
次のように volatile 修飾子を付けるだけでよい。
volatile Resource *resource;
初出: 2001年4月10日