13

このオブジェクトのコンストラクターが終了する前に、オブジェクトへの参照が値を受け取ることがあるJMMのこの機能については、誰もが知っています。

JLS7では、p17.5最終的なフィールド セマンティクスも読むことができます。

フィールドの使用モデルfinalは単純finalです。オブジェクトのコンストラクターでオブジェクトのフィールドを設定します。オブジェクトのコンストラクターが終了する前に、別のスレッドが参照できる場所に構築中のオブジェクトへの参照を書き込まないでください。これに従った場合、オブジェクトが別のスレッドによって認識されると、そのスレッドは常に、そのオブジェクトのfinalフィールドの正しく構築されたバージョンを認識します。(1)

そして、その直後に JLS の例が続きます。これは、non-finalフィールドがどのように初期化されることが保証されていないかを示しています (1Example 17.5-1.1) (2):

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 
       } 
    } 
}

また、この質疑応答でグレイ氏は次のように書いています。

フィールドを としてマークするとfinal、コンストラクターはコンストラクターの一部として初期化を完了することが保証されます。それ以外の場合は、ロックを使用する前にロックを同期する必要があります。(3)


したがって、質問は次のとおりです。

1) ステートメント (1) によると、コンストラクターが終了する前に不変オブジェクトへの参照を共有することは避けるべきです

2) JLS の例 (2) と結論 (3) によると、不変オブジェクトのコンストラクターが終了する前、つまりすべてのフィールドがfinal.

何か矛盾していませんか?


EDIT-1 : 私が正確に言いたいこと。そのような方法で例のクラスを変更すると、そのフィールドyfinal(2) になります。

class FinalFieldExample { 
    final int x; 
    final int y; 
    ...

したがって、reader()メソッドでは、次のことが保証されます。

if (f != null) { 
int i = f.x; // guaranteed to see 3
int j = f.y; // guaranteed to see 4, isn't it???

fもしそうなら、コンストラクターが終了する前に ((1) に従って)オブジェクトへの参照を書き込むことを避ける必要があるのはなぜfですか?

4

4 に答える 4

7

[JLS のコンストラクターとオブジェクト パブリッシュに関する] 矛盾はありませんか?

これらは矛盾しない、わずかに異なる問題だと思います。

JLS 参照は、コンストラクターが終了する前に、他のスレッドが参照できる場所にオブジェクト参照を格納することを考慮しています。たとえば、コンストラクターでは、static他のスレッドで使用されているフィールドにオブジェクトを配置したり、スレッドをフォークしたりしないでください。

  public class FinalFieldExample {
      public FinalFieldExample() {
         ...
         // very bad idea because the constructor may not have finished
         FinalFieldExample.f = this;
         ...
      }
  }

コンストラクターでスレッドを開始しないでください。

  // obviously we should implement Runnable here
  public class MyThread extends Thread {
      public MyThread() {
         ...
         // very bad idea because the constructor may not have finished
         this.start();
      }
  }

すべてのフィールドがfinalクラス内にある場合でも、コンストラクターが終了する前にオブジェクトへの参照を別のスレッドと共有すると、他のスレッドがオブジェクトの使用を開始するまでにフィールドが設定されていることを保証できません。

私の答えは、コンストラクターが終了した後に同期せずにオブジェクトを使用することについて話していました。コンストラクター、同期の欠如、およびコンパイラーによる操作の並べ替えに関しては似ていますが、少し異なる質問です。

JLS 17.5-1 では、コンストラクター内で静的フィールドを割り当てません。別の静的メソッドで静的フィールドを割り当てます。

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

これが決定的な違いです。

于 2013-02-01T19:46:32.753 に答える
4

完全な例では

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
        } 
    } 
}

ご覧のとおりコンストラクターが戻るfまで設定されません。これは、コンストラクターが返された ANDであるため、安全であることを意味します。f.xfinal

次の例では、どちらの値も設定される保証はありません。

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

    public FinalFieldExample() {
        x = 3; 
        y = 4; 
        f = this; // assign before finished.
    } 

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

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

ステートメント(1)によると、コンストラクターが終了する前に不変オブジェクトへの参照を共有することは避けるべきです

オブジェクトを保存した後にオブジェクトが Exception をスローするなど、さまざまな理由 (不変またはその他の理由) により、オブジェクトが構築される前にエスケープへの参照を許可しないでください。

JLS の例 (2) と結論 (3) によると、不変オブジェクトへの参照を安全に共有できるようです。つまり、すべてのフィールドが final の場合です。

オブジェクトが構築された後、不変オブジェクトへの参照をスレッド間で安全に共有できます。

注: 不変フィールドの値は、コンストラクターによって呼び出されるメソッドで設定される前に確認できます。

于 2013-02-01T19:52:25.657 に答える
3

ここでは、構築出口が重要な役割を果たします。JLSは、「cが終了すると、oの最終フィールドfに対するフリーズアクションが発生します」と述べています。コンストラクターの終了前後の参照の公開は大きく異なります。

非公式に

1 constructor enter{

2   assign final field

3   publish this

4 }constructor exit

5 publish the newly constructed object

[2] は、コンストラクターの出口を超えて並べ替えることはできません。したがって、[2] は [5] の後に並べ替えることはできません。

ただし、[2] は [3] の後に並べ替えることができます。

于 2013-02-01T20:53:29.100 に答える
1

ステートメント 1) は、あなたが考えていることを述べていません。どちらかといえば、私はあなたの声明を言い換えます:

1) ステートメント (1) によると、コンストラクターが終了する前に不変オブジェクトへの参照を共有することは避けるべきです

読む

1) ステートメント (1) によると 、コンストラクターが終了する前に可変オブジェクトへの参照を共有することは避けるべきです

ここで、可変とは、非最終フィールドまたは可変オブジェクトへの最終参照を持つオブジェクトです。(変更可能なオブジェクトへの最終的な参照について心配する必要があることを 100% 保証する必要はありませんが、私は正しいと思います...)


別の言い方をすれば、次のものを区別する必要があります。

  • final フィールド (おそらく不変オブジェクトの不変部分)
  • 誰かがこのオブジェクトと対話する前に初期化する必要がある非最終フィールド
  • 誰かがこのオブジェクトを操作する前に初期化する必要のない非最終フィールド

2枚目は問題箇所です。

そのため、不変オブジェクト (すべてのフィールドは ) への参照を共有できますが、オブジェクトを誰でも使用できるようにする前に初期化する必要がある非フィールドをfinal持つオブジェクトには注意が必要です。final

つまり、投稿した編集済みの JLS の例では、両方のフィールドがfinalint j = f.y;あることが最終的なものであることが保証されています。しかし、それが意味することは、オブジェクト f への参照を書くことを避ける必要がないということです。なぜなら、誰かがそれを見る前に、それは常に正しく初期化された状態になるからです。JVM が心配する必要はありません。

于 2013-02-01T19:49:29.963 に答える