コンストラクターをチェーンするとき、JVMの暗黙の記憶障壁はどのように振る舞いますか?
-
22-09-2019 - |
質問
私を参照しています 不完全に構築されたオブジェクトに関する以前の質問, 、二度目の質問があります。ジョン・スキートが指摘したように、コンストラクターの最後に暗黙の記憶障壁があり、それを確実にします 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
Sはそれをやっています。
解決
Java Memory Modelが述べているので、それは安全だと思います。
させて o オブジェクトになります c のコンストラクターになります o 最終フィールド f 書かれた。最終フィールドでのフリーズアクション f の o いつ行われます c 通常または突然の出口。 1つのコンストラクターが別のコンストラクターを呼び出し、呼び出されたコンストラクターが最終フィールドを設定すると、最終フィールドのフリーズが呼び出されたコンストラクターの最後に行われる場合に注意してください。
他のヒント
オブジェクトは、コンストラクターが仕上げられたときに完全に初期化されると見なされます。
これは、チェーンコンストラクターにも適用されます。
コンストラクターに登録する必要がある場合は、リスナーを静的インナークラスとして定義します。これは安全です。
2番目のバージョンは正しくありません。なぜなら、「この」参照が建設プロセスから逃れることを許可しているからです。 「この」エスケープを持つことは、最終フィールドに安全性を与える初期化の安全保証を無効にします。
暗黙の質問に対処するために、建設の最後の障壁は、オブジェクト構造の最後でのみ発生します。読者がインランスについて提供した直観は有用なものです。 Javaメモリモデルの観点からは、メソッドの境界は存在しません。
編集 コンパイラがプライベートコンストラクターを挿入することを提案したコメントの後(私はその最適化については考えていませんでした)、コードが安全でないという可能性があります。そして、安全でないマルチスレッドコードの最悪の部分は、それが機能しているように見えることです。そのため、完全に回避する方が良いです。さまざまなトリックをプレイしたい場合(何らかの理由で工場を避けたい)ラッパーを追加して、内部実装オブジェクトのデータの一貫性を保証し、外部オブジェクトに登録することを検討してください。
私の推測では、それは壊れやすいですが大丈夫だと思います。コンパイラは、内部コンストラクターが他のコンストラクター内からのみ呼び出されるかどうかを知ることができないため、結果が内部コンストラクターのみを呼び出すコードが正しいことを確認する必要があります。そこに配置されます。
コンパイラは、すべてのコンストラクターの最後にメモリバリアを追加すると思います。問題はまだそこにあります:あなたは this
それが完全に構築される前に他のコード(おそらく他のスレッド)への参照 - それは悪いことです - しかし、残っている「構築」がリスナーを登録することである場合、オブジェクト状態はこれまでと同じくらい安定します。
解決策はです 壊れやすい その他の日には、あなたまたは他のプログラマーがオブジェクトに別のメンバーを追加する必要があり、チェーンズコンストラクターが並行性のトリックであることを忘れるかもしれません。アプリケーションで潜在的なデータレースを検出するのは難しいので、その構成を避けようとします。
ところで:推測された安全性が間違っている可能性があります。コンパイラがどれほど複雑/スマートであるか、そしてメモリの壁(または同様)が最適化しようとするものであるかどうかはわかりません...コンストラクターはプライベートであるため、コンパイラはそれがあることを知るのに十分な情報を持っています他のコンストラクターからのみ呼び出され、それは内部コンストラクターでは同期メカニズムが必要ないと判断するのに十分な情報です...
C-TORの脱出オブジェクト参照は、不完全に構築されたオブジェクトを公開できます。これは真実です 出版物がコンストラクターの最後の声明である場合.
C-TORインラインが実行されていても、SafeListenerは同時環境でOKに動作しない場合があります(これはそうではないと思います - プライベートC-TORにアクセスして反射を使用してオブジェクトを作成することを考えてください)。