14

不完全に構築されたオブジェクトに関する以前の質問を参照して、2 つ目の質問があります。Jon Skeet が指摘したように、コンストラクターの最後には暗黙的なメモリ バリアがあり、finalフィールドがすべてのスレッドから見えるようになっています。しかし、コンストラクターが別のコンストラクターを呼び出すとどうなりますか。それらのそれぞれの終わりにそのようなメモリバリアがありますか、それとも最初に呼び出されたものの最後にのみありますか? つまり、「間違った」ソリューションが次の場合です。

public class ThisEscape {
    public ThisEscape(EventSource source) {
        source.registerListener(
            new EventListener() {
                public void onEvent(Event e) {
                    doSomething(e);
                }
            });
    }
}

正しいものは、ファクトリ メソッドのバージョンです。

public class SafeListener {
    private final EventListener listener;

    private SafeListener() {
        listener = new EventListener() {
            public void onEvent(Event e) {
                doSomething(e);
            }
        }
    }

    public static SafeListener newInstance(EventSource source) {
        SafeListener safe = new SafeListener();
        source.registerListener(safe.listener);
        return safe;
    }
}

以下も機能しますか?

public class MyListener {
    private final EventListener listener;

    private MyListener() {
        listener = new EventListener() {
            public void onEvent(Event e) {
                doSomething(e);
            }
        }
    }

    public MyListener(EventSource source) {
        this();
        source.register(listener);
    }
}

更新:本質的な問題は、上記のプライベート コンストラクターthis()を実際に呼び出すことが保証されていることです(この場合、意図した場所にバリアがあり、すべてが安全です)、またはプライベート コンストラクターがパブリックコンストラクターにインライン化される可能性はありますか? 1 つのメモリ バリアを節約するための最適化 (この場合、パブリック コンストラクターの最後までバリアはありません)?

のルールはthis()正確にどこかに定義されていますか? そうでない場合は、チェーンされたコンストラクターのインライン化が許可されていると想定する必要があると思いますjavac

4

5 に答える 5

6

Javaメモリモデルは次のように述べているため、安全だと思います。

oをオブジェクト、cをoのコンストラクタとし、最終フィールドfを記述します。oの最終フィールドfに対するフリーズ アクションは、 cが通常どおりまたは突然終了したときに発生します。あるコンストラクターが別のコンストラクターを呼び出し、呼び出されたコンストラクターが final フィールドを設定する場合、呼び出されたコンストラクターの最後で final フィールドのフリーズが発生することに注意してください。

于 2012-04-24T16:28:03.183 に答える
3

コンストラクターが終了すると、オブジェクトは完全に初期化されたと見なされます。

これは、連鎖コンストラクターにも適用されます。

コンストラクターに登録する必要がある場合は、リスナーを静的内部クラスとして定義します。これは安全です。

于 2010-03-25T08:13:31.623 に答える
3

2 番目のバージョンは正しくありません。これは、「this」参照が構築プロセスから逃れることを許可しているためです。「this」エスケープを使用すると、最終フィールドに安全性を与える初期化の安全性の保証が無効になります。

暗黙の疑問に答えるために、構築の最後のバリアは、オブジェクトの構築の最後にのみ発生します。ある読者がインライン化について提供した直感は役に立ちます。Java メモリ モデルの観点からは、メソッド境界は存在しません。

于 2010-03-25T14:16:59.110 に答える
1

編集プライベートコンストラクターをインライン化するコンパイラーを示唆するコメントの後(私はその最適化については考えていませんでした)、コードが安全でなくなる可能性があります。また、安全でないマルチスレッドコードの最悪の部分は、機能しているように見えることです。したがって、完全に回避することをお勧めします。さまざまなトリックを実行したい場合(何らかの理由でファクトリを避けたい場合)は、ラッパーを追加して、内部実装オブジェクトのデータの一貫性を保証し、外部オブジェクトに登録することを検討してください。


私の推測では、それは壊れやすいでしょうが、大丈夫です。コンパイラーは、内部コンストラクターが他のコンストラクター内からのみ呼び出されるかどうかを知ることができないため、内部コンストラクターのみを呼び出すコードに対して結果が正しいことを確認する必要があります。したがって、使用するメカニズム(メモリバリア?)はすべてです。そこに配置されます。

コンパイラーは、すべてのコンストラクターの最後にメモリバリアを追加すると思います。問題はまだあります:this完全に構築される前に他のコード(おそらく他のスレッド)への参照を渡しています-それは悪いです-しかし、残っている唯一の「構築」がリスナーの登録である場合、オブジェクト状態はこれまでになく安定しています。

解決策は壊れやすく、あなたや他のプログラマーがオブジェクトに別のメンバーを追加する必要があり、連鎖コンストラクターが並行性のトリックであることを忘れて、パブリックコンストラクターのフィールドを初期化することを決定する可能性があります。そのため、アプリケーションで潜在的なデータ競合を検出するのが困難になるため、その構成を回避しようとします。

ところで:推測された安全性は間違っているかもしれません。コンパイラがどれほど複雑/スマートであるか、メモリバリア(など)が最適化を試みることができるかどうかはわかりません...コンストラクタはプライベートであるため、コンパイラはそれを知るのに十分な情報を持っています他のコンストラクターからのみ呼び出されます。これは、内部コンストラクターで同期メカニズムが不要であることを判断するのに十分な情報です。

于 2010-03-25T08:22:00.923 に答える
1

c-torでオブジェクト参照をエスケープすると、不完全に構築されたオブジェクトを公開できます。これは、パブリケーションがコンストラクターの最後のステートメントである場合でも当てはまります。

SafeListenerは、c-torインライン化が実行されたとしても、並行環境では正常に動作しない可能性があります(これは、そうではないと思います-プライベートc-torにアクセスしてリフレクションを使用してオブジェクトを作成することを検討してください)。

于 2010-03-25T09:30:22.547 に答える