8

これがあると言う

// This is trivially immutable.
public class Foo {
    private String bar;
    public Foo(String bar) {
        this.bar = bar;
    }
    public String getBar() {
        return bar;
    }
}

このスレッドが危険な理由は何ですか? この質問に続きます。

4

4 に答える 4

12

Foo安全に公開されると、スレッドセーフになります。たとえば、このプログラムは「安全ではない」と出力する可能性があります (おそらく hotspot/x86 の組み合わせは使用しないでしょう) bar

public class UnsafePublication {

    static Foo foo;

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (foo == null) {}
                if (!"abc".equals(foo.getBar())) System.out.println("unsafe");
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                foo = new Foo("abc");
            }
        }).start();
    }
}
于 2013-04-17T14:13:58.577 に答える
7

JVM の最適化により、同じスレッドで重要でない限り、記述された順序で操作が実行されると想定することはできません。そのため、コンストラクターを呼び出して、結果のオブジェクトへの参照を別のスレッドに渡すと、JVMは同じスレッド内で必要になる前に foo.bar の値を実際には書き込まない場合があります。

つまり、マルチスレッド環境では、コンストラクターの値が書き込まれる前に getBar メソッドを呼び出すことができました。

于 2013-04-17T14:13:37.190 に答える
6

ほとんどの場合、あなたは今までに答えを得ているでしょうが、私の説明も追加したかったことを確認するために.

オブジェクト(あなたの場合)をスレッドセーフにするためには、次のことを行う必要があります。

  • 不変であること
  • 安全に公開する

不変- あなたはそうしました。バーが設定されると、バーを変更する方法はありません。ここではかなり明白です。

無事公開。例によると、コードは安全に公開されていません。bar は最終的なものではないため、コンパイラは適切と判断したときに自由に並べ替えることができます。コンパイラは、バーへの書き込みの前に、 Foo インスタンスへの参照を公開 (メイン メモリへの書き込み) できます。これは、バーが null であることを意味します。したがって、最初に Foo への参照がメイン メモリに書き込まれ、次にbar への書き込みが行われます。これら 2 つのイベントの間に、別のスレッドが古いバーを null と見なすことがあります。

それに final を追加すると、JMM は次のことを保証します。

final フィールドの値は、構築されたオブジェクトにアクセスする他のスレッドから見えることが保証されています。

または、最終フィールドによって並べ替えが妨げられます。したがって、その変数を final にすると、スレッド セーフが保証されます。

于 2013-04-24T08:49:25.807 に答える
1

コメントに投稿されたリンクから:

class FinalFieldExample { 
    final int x;
    int y; 
    static FinalFieldExample f;

    public FinalFieldExample() {
        x = 3; 
        y = 4; 
    } 

    static void writer() {
        f = new FinalFieldExample();
    } 

    static void reader() {
        if (f != null) {
            int i = f.x;  // guaranteed to see 3  
            int j = f.y;  // could see 0
        } 
    } 
}

1 つのスレッドが を呼び出しwriter()、別のスレッドが を呼び出す場合がありますreader()。reader() の if 条件は true と評価される可能性がありますが、y が final ではないため、オブジェクトの初期化が完全に終了していない可能性があり(そのため、オブジェクトはまだ安全に公開されていません)、int j = 0初期化されていないために発生する可能性があります。

于 2013-04-17T14:35:01.607 に答える