関数名の付け方 Java オブジェクト参照の不適切なパブリケーション




関数 名 文字 列 (4)

public class Holder {
  private int n;
  public Holder(int n) { this.n = n; }

  public void assertSanity() {
    if (n!=n)
      throw new AssertionError("This statement is false");
  }
}

1つのスレッドがHolderインスタンスを作成し、参照をassertSanityを呼び出す別のスレッドに渡したとしassertSanity

this.n内のthis.nへの代入は、1つのスレッドで行われます。 また、別のスレッドでn読み込みが2回発生します。 ここでの唯一の先行関係は、2つの読み込みの間にあります。 代入と読み込みのいずれかを含む先験的な関係はありません。

this.n = n関係がなければ、ステートメントはさまざまな方法で並べ替えることができます。したがって、スレッド1つの観点から、 this.n = nはコンストラクターが返された後に発生する可能性があります。

これは、最初の読み取りの後、2番目のスレッドの前に2番目のスレッドで割り当てが発生しているように見え、結果が一致しないことを意味します。 finalはn finalにすることで防ぐことができます。これにより、コンストラクタが終了する前に値が代入されることが保証されます。

以下の例は、Brian Goetz、Chapte 3、第3.5.1節の本 "Java Concurrency in Practice"の本です。 これは、オブジェクトの不適切な発行の例です

class someClass {
    public Holder holder;

    public void initialize() {
        holder = new Holder(42);
    }
}

public class Holder {
  private int n;
  public Holder(int n) { this.n = n; }

  public void assertSanity() {
    if (n!=n)
      throw new AssertionError("This statement is false");
  }
}

Holderは、一貫性のない状態で別のスレッドに表示され、別のスレッドが部分的に構築されたオブジェクトを監視できることが示されています。 これはどうしたらできますか? 上の例を使ってシナリオを教えてください。

また、スレッドが、フィールドを最初に読み込んだときに失効した値を表示し、次回以降に日付値が増えることがある場合があります。そのため、assertSanityがアサーションエラーをスローする可能性があります。 アサーションエラーはどのようにスローされますか?

この問題を解決する1つの方法は、変数を 'n'にすることによってホルダーを不変にすることです。 今のところ、ホルダーは免除可能ではないが効果的に不変であると仮定しよう。 このオブジェクトを安全に公開するには、ホルダーの初期化を静的にして、volatile(静的な初期化とvolatileまたは単にvolatileの両方)として宣言する必要がありますか? 何かのようなもの

public class someClass {
    public static volatile Holder holder = new Holder(42);

}

あなたの助けを前もってありがとう。


HolderクラスはOKですが、 someClassクラスは、 holderインスタンス変数がnull initialize()の作成と呼び出しの間に、不整合な状態で表示されnull


あなたが尋ねる問題は、JVMの最適化と、単純なオブジェクト作成という事実によって引き起こされます。

MyClass obj = new MyClass()

必ずしもステップで実行されるわけではありません。

  1. ヒープ上のMyClassの新しいインスタンスのメモリを予約する
  2. コンストラクタを実行して内部プロパティ値を設定する
  3. ヒープ上のアドレスに 'obj'リファレンスを設定する

いくつかの最適化目的のために、JVMは以下の手順でそれを行うことができます:

  1. ヒープ上のMyClassの新しいインスタンスのメモリを予約する
  2. ヒープ上のアドレスに 'obj'リファレンスを設定する
  3. コンストラクタを実行して内部プロパティ値を設定する

したがって、2つのスレッドがMyClassオブジェクトにアクセスしたいと考えているとします。 最初のものが作成されますが、JVMのために「最適化された」一連のステップが実行されます。 私たちが重大な問題を抱える可能性がある以上、ステップ1と2(ただし、3はしません)だけを実行する場合。 2番目のスレッドがこのオブジェクトを使用する場合(ヒープ上のメモリの予約された部分をすでに指しているためnullにはなりません)、そのプロパティは間違っていて厄介なことにつながります。

この最適化は、参照が揮発性である場合には発生しません。


オブジェクトの作成には、非アトミックな関数がいくつかあると想像することができます。 最初に、ホルダーを初期化して公開します。 しかし、すべてのプライベートメンバーフィールドを初期化してパブリッシュする必要もあります。

まあ、JMMにはholderのフィールドの書き込みと発行のルールがないので、 initialie()holderフィールドが書き込まれる前に発生しinitialie() 。 つまり、 holderがnullではないにもかかわらず、メンバーフィールドが他のスレッドからまだ見えないことは合法です。

あなたは何かのように見えるかもしれません

public class Holder{
    String someString = "foo";
    int someInt = 10;
} 

holderはnullでなくてもよく、 someStringはnull someIntは0 someIntません。

x86アーチの下では、これは私が知っていることから、起こることは不可能ですが、他人の場合はそうでないかもしれません。

だから次の質問かもしれないWhy does volatile fix this? JMMは、揮発性ストアの前に発生したすべての書き込みは、後続のすべての揮発性フィールドのスレッドから見えると述べています。

したがって、 holderが揮発性であり、 holderがnullでないことがわかっている場合、揮発性ルールに基づいて、すべてのフィールドが初期化されます。

このオブジェクトを安全に公開するには、ホルダーの初期化を静的にし、volatileとして宣言する必要があります

はい。 holder変数がnullでない場合は、すべての書き込みが表示されるため、前述したとおりです。

アサーションエラーはどのようにスローされますか?

スレッドがnullでないことをスレッドに通知し、メソッドを入力するときにassertionErrorを呼び出すと、最初の時刻を0 (デフォルト値)にすると、 nの2番目の読み込みで最初のスレッドからの書き込みが表示されます。





concurrency