1. ホーム

[解決済み】このJavaプログラムは、明らかに終了すべきではない(しなかった)にもかかわらず、なぜ終了するのでしょうか?)

2022-04-11 19:51:32

質問

今日、私の研究室で繊細な操作が完全に失敗してしまいました。電子顕微鏡のアクチュエータが境界を越えてしまい、連鎖的に1200万ドルの装置を失ってしまったのです。不具合のあったモジュールの40K以上のラインをこれに絞り込みました。

import java.util.*;

class A {
    static Point currentPos = new Point(1,2);
    static class Point {
        int x;
        int y;
        Point(int x, int y) {
            this.x = x;
            this.y = y;
        }
    }
    public static void main(String[] args) {
        new Thread() {
            void f(Point p) {
                synchronized(this) {}
                if (p.x+1 != p.y) {
                    System.out.println(p.x+" "+p.y);
                    System.exit(1);
                }
            }
            @Override
            public void run() {
                while (currentPos == null);
                while (true)
                    f(currentPos);
            }
        }.start();
        while (true)
            currentPos = new Point(currentPos.x+1, currentPos.y+1);
    }
}

私が得ている出力のいくつかのサンプル。

$ java A
145281 145282
$ java A
141373 141374
$ java A
49251 49252
$ java A
47007 47008
$ java A
47427 47428
$ java A
154800 154801
$ java A
34822 34823
$ java A
127271 127272
$ java A
63650 63651

ここには浮動小数点演算がなく、Javaでは符号付き整数がオーバーフローに対してうまく動作することが知られているので、このコードには何も問題がないと思う。しかし、プログラムが終了条件に達していないことを示す出力があるにもかかわらず、終了条件に達しています(両方達しています は到達していないのでしょうか)。なぜでしょうか?


環境によっては起きないことに気づきました。私は OpenJDK 6、64ビットLinux上。

解決方法は?

<ブロッククオート

明らかに、currentPosへの書き込みは、読み込みの前に起こることはないのですが、それがどうして問題になるのかわかりません。

currentPos = new Point(currentPos.x+1, currentPos.y+1); は、デフォルト値を xy (0)とし、コンストラクタでそれらの初期値を書き込みます。オブジェクトは安全に公開されないので、これら4つの書き込み操作はコンパイラ/JVMによって自由に並べ替えが可能です。

したがって、読み取り側のスレッドから見ると、以下のように読み取ることは合法的な実行です。 x は新しい値を持つが y をデフォルトの値である0に変更します。に到達するまでに println 文(ちなみにこれは同期しているので、読み込み操作に影響します)は、変数が初期値を持ち、プログラムは期待される値を表示します。

マーキング currentPos として volatile は、オブジェクトが事実上不変であるため、安全な発行を保証します。 volatile の保証は十分ではなく、また一貫性のないオブジェクトを目にすることになるかもしれません。

別の方法として Point を使わなくても、安全な公開が保証されます。 volatile . 不変性を実現するためには、単に xy の最終的なものです。

余談ですが、すでに述べたように synchronized(this) {} は、JVMによってno-opとして扱われる可能性があります(あなたはこの動作を再現するためにそれを含めたと理解しています)。