凪瀬 Blog
Programming SHOT BAR

目次

Blog 利用状況
  • 投稿数 - 260
  • 記事 - 0
  • コメント - 46913
  • トラックバック - 192
ニュース
広告
  • Java開発者募集中
  • 経歴不問
  • 腕に自信のある方
  • 富山市内
  • (株)凪瀬アーキテクツ
アクセサリ
  • あわせて読みたい
凪瀬悠輝(なぎせ ゆうき)
  • Java技術者
  • お茶好き。カクテル好き。
  • 所属は(株)凪瀬アーキテクツ
  • Twitter:@nagise

書庫

日記カテゴリ

 

前回 から別の方向に大きく逸れてしまいましたが、別スレッドでの結果を取得する第2回です。

下記コードはThread.join()を使って他のスレッドの終わりを待って値を取得しようというサンプルです。

/**
 * joinを用いて他のThreadの結果を取得するサンプル。
 * 同期が行われていないので潜在的な危険を孕んでいる?
 */
public class JoinTest extends Thread {
    /** return用の値 */
    private int value;

    /** Threadでの処理 */
    @Override
    public void run() {
        // 実処理
        // ...

        this.value = 0;
    }
    /**
     * 値取得用のメソッド。
     */
    public int getValue() {
        return this.value;
    }

    public static void main(String[] argsthrows Exception {
        JoinTest t = new JoinTest();
        t.start();

        // Threadの終了を待つ
        t.join();
        // 結果の取得
        int ret = t.getValue();
    }
}

私の理解が正しければ、このコードは正しい値を返さない可能性があるのではないでしょうか?

double-checked locking問題と同じ?

どうにもこのコード、 double-checked locking問題 と同じ匂いがするのです。

Thread.join()による待機の完了は、対象スレッドのどの処理よりも後となることは保証されています。 では、上記サンプルのように対象スレッド内でフィールドに書き出しておいて、 join()で終わりを待って値を読み込めば正しく値が読み出せるように思えます。

ところが、Thread.join()は同期されるわけではないので、この時点でメモリが同期されていることを 期待してはいけないのではないでしょうか?

受け渡し用のフィールドがvolatileでありさえすれば、問題は起こらないことでしょう。 しかし非volatileでかつ非synchronizedの場合は…?

クラス設計上の問題

wait()方式に比べた弱点は、クラスとして完結させにくい点です。

少なくとも、Thread#start()の後にThread#join()して、それから値を取得してくださいね、 なんて約束をクラスの利用者に守ってもらうというのは防衛的ではないですよね。

防衛的なクラス設計にするのであれば、getValue()メソッドの内部で完了を待機したいところです。 そうなると、Thread#join()を実行するために、自身が実行されているThreadのインスタンスが必要となります。 結局、Thread.start()までをもクラス内部に抱え込まないと安全で利便性の高いクラスにはできません…。

このように、クラス設計において、wait()方式に比べると若干面倒が多いように思えます。
InterruptedExceptionへの対応が必要な点や、同期が必要な点はwait()と変わりませんから メリットは特に無いのではないでしょうか。Threadはリソースも多く食いますしね。

投稿日時 : 2007年8月27日 21:12
コメント
  • # re: スレッドから値を返すには - その2
    かつのり
    Posted @ 2007/08/27 21:38
    joinは同期化されますよ。
    joinの呼び出しはjoin(0)にディスパッチするんですが、
    join(long)が同期化されます。

    とはいえdouble-checked-lockingの問題を含んでいそうな感じはしますね。
    確実性を求めるなら、やっぱりvolatileっすか。
  • # re: スレッドから値を返すには - その2
    凪瀬
    Posted @ 2007/08/27 22:56
    ドキュメントに同期について何も書かれていないんですよね…
    http://java.sun.com/javase/ja/6/docs/ja/api/java/lang/Thread.html#join(long)

    ソースを見ると確かにjoin(long)とjoin(long, int)はsynchronizedキーワードが付いています。
    内部実装的にはThreadのインスタンスをロックオブジェクトとしてwait()をしているのですね。知らなかった。

    そうなると、join()しているスレッドはこの時点でメインメモリと同期することになりますね。
    問題はjoin()されていた側のスレッドですが、スレッドの終了ってのは同期されるんでしたっけ?

    一応、実験して確認しましたが、run()では自身のモニタを持っていませんでした。スレッド終了に際して特殊な同期の仕様がないのであれば、join()の同期のロックオブジェクトがthisだから、join()時点ではrun()内の情報がメインメモリに反映されていない可能性は残るように思えます。
  • # re: スレッドから値を返すには - その2
    かつのり
    Posted @ 2007/08/28 0:07
    ところで、またスレッド関連の別の話題を振ってもいいすか?
    (実はスレッドとかGUIって苦手なんですよね・・・)

    InterruptedExceptionをキャッチしたときに、
    Thread.currentThread().interrupt()呼ぶのが作法ってよく聞くんですが、
    どういう意味があってやるんですかね?イマイチわかりません(><)
  • # re: スレッドから値を返すには - その2
    凪瀬
    Posted @ 2007/08/28 0:32
    そのネタは別枠でやりますよ~。
    Javaの並列処理の基本の2か3ぐらいでw
  • # re: スレッドから値を返すには - その2
    なちゃ
    Posted @ 2007/08/28 3:27
    http://www-06.ibm.com/jp/developerworks/java/040514/j_j-jtp03304.html
    の「何の前に何が起きるのか?」のところに、
    >スレッドでの全操作は、そのスレッドのThread.join()から成功(success)して戻る他のどのスレッドよりもhappens-beforeである
    とあります。
    この辺り読むとわかりますが、こういう場合の事前発生とは、Java言語上から見える操作について事前発生するということで、
    当然事前発生した結果は必ず見えることが保証されています。
    でないと意味がないですからね。
  • # re: スレッドから値を返すには - その2
    凪瀬
    Posted @ 2007/08/28 9:57
    そちらは参考にしたのですが、happens-beforeを詳しく理解できていないのですよね。
    順序が保障されることは分かるのですが、メモリ同期との絡みが掴めなくて…

    happens-beforeであるということは、それ以前の操作がメインメモリに同期されていると理解してよいのでしょうか?
    であれば、join()で戻った直後にrun()での操作をvolatileなしに参照しても大丈夫ということになりますよね。
  • # re: スレッドから値を返すには - その2
    yamasa
    Posted @ 2007/08/28 10:12
    なちゃさんの述べていることと被りますが、Thread#join()が同期ポイントとなることは
    Java言語仕様にも明確に定義されています。

    http://java.sun.com/docs/books/jls/third_edition/html/memory.html#17.4.4

    17.4.4 Synchronization Order

    The final action in a thread T1 synchronizes-with any action
    in another thread T2 that detects that T1 has terminated.
    T2 may accomplish this by calling T1.isAlive() or T1.join().

    なので上述のコードは正しく動作しますね。
  • # re: スレッドから値を返すには - その2
    凪瀬
    Posted @ 2007/08/28 11:16
    どうやら、該当引用部分は把握したが、前後の章を前提知識として正しく把握していなかったので本文のような意見を持ったようです。

    Java言語仕様3版の17.4.3 プログラムとプログラムの順序の項で「順序の整合性」についての解説がありますが、ここを理解できていなかった。

    「順序の整合性は、可視性とプログラムの実行順序に対する強力な保障となる。順序の整合性がある実行中にはプログラムの順序と整合性があり、かつ個々の動作がアトミック(不可分)でありすべてのスレッドから即座に可視化されるような、個々の動作全てに対する全体的な順序が存在する。」

    とあります。その上で、17.4.4にjoinおよびisAliveなどスレッドの終了を検知する場合、スレッドの最後の操作と検知操作の順序の整合性は保障されているわけですから、大丈夫ということですね。

    可視性への言及からみてもメモリの同期は行われているとしてよいのですね。
  • # kqRaowKGXvdEDHaQuH
    http://www.tat2x.com/
    Posted @ 2011/12/27 19:42
    Thanks:) Cool topic, write more often! You manage with it perfctly:D
  • # NYKxChudhxrdMm
    http://www.healthinter.org/health/page/clomid.php
    Posted @ 2011/12/29 21:03
    Gripping! I would like to listen to the experts` views on the subject!!...
  • # WIHNeQAJcjU
    http://www.luckyvitamin.com/m-207-optimum-nutritio
    Posted @ 2012/01/07 9:34
    Hooray! the one who wrote is a cool guy..!
  • # re: スレッドから値を返すには - その2
    Nice
    Posted @ 2013/03/26 2:16
    ソースを見ると確かにjoin(long)とjoin(long, int)はsynchronizedキーワードが付いています。
    内部実装的にはThreadのインスタンスをロックオブジェクトとしてwait()をしているのですね。知らなかった。

    そうなると、join()しているスレッドはこの時点でメインメモリと同期することになりますね。
    問題はjoin()されていた側のスレッドですが、スレッドの終了ってのは同期されるんでしたっけ?
タイトル
名前
Url
コメント