- ベストアンサー
コンストラクタの処理と参照が返るのはどちらが先?
- コンストラクタの処理と参照が返るのはどちらが先?この質問では、コンストラクタが起動する前に参照が返される可能性がある問題について検討されています。
- 具体的には、コンストラクタが終了する前に別のスレッドで参照が行われてしまい、未初期化のシングルトンへの参照が返される可能性がある問題です。
- 質問者は自身の環境でデバッガを用いて確認しましたが、コンストラクタが終了するまでは参照がnullであることが確認できました。しかしこの問題はVMのバージョンや再現性の問題かもしれません。
- みんなの回答 (4)
- 専門家の回答
質問者が選んだベストアンサー
# 有名なネタですよね…。 Javaの場合(C++等ではまた別の話)、VM仕様上out-of-order書き込みが許されるので、 同期がとれる「保証がない」(常に成立するとは限らない)と。 「常にそうなる」というものではなくて、JavaVMの仕様上(≒理論上)、 環境などにより、そうなることがありうるというものなので、 デバッガでとめてみても、多くの場合は質問者さんが確認したような結果になると思いますよ。 # 現実には絶対に現象がでない環境もあれば、出る可能性がある環境もありえる。 # そもそも、他言語などの一般的なロジックとしてはDouble-lockは有効ですし。 そして、そうなってもいいことが許されている以上、 もしもソースがそうならないことを前提に書かれていて、なってしまった場合、 そのソースのバグであって、VMに文句を言える筋ではないので、 そういう書き方は(Javaでは)好ましくないと。
その他の回答 (3)
- kacchann
- ベストアンサー率58% (347/594)
#3です。捕捉。 --- 読んでみて、わかりやすかった資料: ・『デザインパターン入門 マルチスレッド編』増補改訂版 http://www.hyuki.com/dp/dp2.html の[付録B] (「Java言語仕様」の"メモリモデル"の改訂を受けて、 この本も"旧版"の該当箇所を改訂している) ・http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html の 「What is a memory model, anyway? 」 「Do other languages, like C++, have a memory model?」 ・あと「Java言語仕様 第3版」の17章にも 「reordering」の話が、わりとていねいに書かれているみたいです。 ちゃんと読んでませんが・・・。 (「Java言語仕様 第2版」とだいぶ様子が違うかんじ)
- kacchann
- ベストアンサー率58% (347/594)
詳しくないので解説できませんが・・・ ↓わかりやすそうな説明。 http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#reordering (「as-if-serial semantics」というのは、 たぶん 「ある命令群があって、 それがシングルスレッド上で実行されたと"仮定した"場合、 その"結果"が変わらないのであれば、 その命令群に対し、 どんな「順序変更(reordering)」をしても構わない」 という意味・・・のような気がスルンですが)
- ngsvx
- ベストアンサー率49% (157/315)
マルチスレッドで動いたときの話ですね。 あまり自信はありませんが、、、 スレッド1とスレッド2がほぼ同時(スレッド2が若干遅く)に実行した場合で、 1.スレッド1が instance = new Singleton(); を実行 2.スレッド2が instance = new Singleton(); を実行開始 3.スレッド1がreturnで抜ける 4.スレッド2が instance = new Singleton(); を実行終了 というタイミングがあるということだと思います(たぶん)。
補足
引用が足りなかったようですみません。 スレッドセーフなシングルトンの実装手段として、次のDouble-checkedはダメだ という記事なのですが、 | Listing 4. Double-checked locking example | | public static Singleton getInstance() | { | if (instance == null) //0 | { | synchronized(Singleton.class) { //1 | if (instance == null) //2 | instance = new Singleton(); //3 | } | } | return instance; | } //3の箇所で、instanceに値が入るのは、コンストラクタが起動する直前なので、コンストラクタがオブジェクトの初期化を完了しないうちに、別のスレッドが//0の位置に来ると、instance == null が偽となって、初期化の終わっていないオブジェクトを返してしまう、 という解釈なのですが、 Eclipseでコンストラクタの中にブレークポイントを設定し、 別のスレッドで(Runnableで動かしてます)は//0のところでとめて評価内容を見てみると、記事の説明とは違って、instanceはまだnullでした。 記事の書かれたときとVMの実装がちがうのか、私の解釈が間違っているのか、再現方法に問題があるのか、皆さんにも試していただきたくて質問いたしました。
お礼
特定のVMの実装上は問題なくても、VMの仕様が保証していないので、 この件のダブルチェックパターンは使うべきでないということだったんですね。 よくわかりました。ありがとうございます。 しかし、このような罠のような仕様があると、普段書いている foo = new Foo(); が、本当に安全なのか自身がなくなっちゃいます。何を信じてよいものかという気になります。 はぁ・・・(ため息)