「リリースとエスケープ」は「Java並行プログラミングの実践」で紹介されています。最初にリリースを紹介し、次にこの参照エスケープに焦点を当てます。
1.リリース
オブジェクトの「公開」とは、現在のスコープ外のコードでオブジェクトを使用できるようにすることを意味します。
例えば:
①オブジェクトへの参照を他のコードがアクセスできる場所に保存します
②非プライベートメソッドで参照を返す
③他のクラスのメソッドへの参照を渡す
いくつかの具体例を見てみましょう。
①オブジェクトの参照をパブリック静的変数に保存して、すべてのクラスとスレッドがオブジェクトを表示できるようにします
import java.util.*;
/**
* Secrets
*
* Publishing an object
*
* @author Brian Goetz and Tim Peierls
*/
class Secrets {
public static Set<Secret> knownSecrets;
public void initialize() {
knownSecrets = new HashSet<Secret>();
}
}
class Secret {
}
上記のコードに示されているように、新しいHashSetオブジェクトがinitializeメソッドでインスタンス化され、オブジェクトへの参照がknownSecretsに保存されて、オブジェクトが公開されます。それを理解する方法は?シークレットオブジェクトをコレクションknownSecetsに追加すると、どのコードもこのコレクションをトラバースしてこの新しいシークレットオブジェクトへの参照を取得できるため、このオブジェクトは長期間公開されます。
②非プライベートメソッドから参照を返し、返されたオブジェクトも公開します
/**
* UnsafeStates
* <p/>
* Allowing internal mutable state to escape
*
* @author Brian Goetz and Tim Peierls
*/
class UnsafeStates {
private String[] states = new String[]{
"AK", "AL" /*...*/
};
public String[] getStates() {
return states;
}
}
上記のコード配列の状態は、プライベートであるはずの変数が解放されたため、スコープをエスケープしました。
③オブジェクトを外部メソッドに渡す場合は、変数を公開するのと同じです。
クラスCの「外部メソッド」について説明します。Cの場合、「外部メソッド」とは、他のクラスで定義されたメソッドやクラスCで書き換え可能なメソッド(どちらもプライベートメソッド[プライベート]またはファイナルメソッド[ファイナル])
④内部クラスを公開すると、内部クラスはその外部クラスのこの参照を自動的に保持します
第二に、脱出
エスケープとは、解放されるべきではないオブジェクトの解放を指します。
この参照のエスケープは、オブジェクトが構築される前にこの参照が解放されることを意味します。他のスレッドがこのエスケープされた参照を介して初期化されていない不完全なオブジェクトにアクセスしたり、他のオブジェクトが初期化されたオブジェクトにアクセスしたりして、不整合が発生する可能性があるため、スレッドセーフの問題が発生します。
本で提供されている例を見てみましょう:
/**
* ThisEscape
* <p/>
* Implicitly allowing the this reference to escape
*
* @author Brian Goetz and Tim Peierls
*/
public class ThisEscape {
public ThisEscape(EventSource source) {
source.registerListener(new EventListener() {
public void onEvent(Event e) {
doSomething(e);
}
});
}
void doSomething(Event e) {
}
interface EventSource {
void registerListener(EventListener e);
}
interface EventListener {
void onEvent(Event e);
}
interface Event {
}
}
ThisEscapeが示すように、ThisEscapeがEventListenerを公開すると、ThisEscapeインスタンス自体も暗黙的に公開されます。これは、この内部クラスのインスタンスにThisEscapeインスタンスへの暗黙の参照が含まれているためです。オブジェクトがオブジェクトのコンストラクターから解放されると、オブジェクトを解放するステートメントがコンストラクターの最後の行にある場合でも、まだ構築されていないオブジェクトのみが解放されます。
この本は、この参照がエスケープされ、コンストラクターでスレッドが開始される状況についても言及しています。オブジェクトがコンストラクターでスレッドを作成すると、明示的に作成されるか(コンストラクターに渡すことによって)、暗黙的に作成されるか(ThreadまたはRunnableはオブジェクトの内部クラスであるため)、この参照は新しく作成されたスレッド共有になります。オブジェクトが完全に構築される前に、新しいスレッドはそれを見ることができます。
この参照をエスケープする理由を要約します。
最初のケース:内部クラス
①コンストラクタで内部クラスを作成しました
②コンストラクタでは常に内部クラスを公開します
解決:
①②が同時に表示されるのを避け、コンストラクターが完了するまで待ち、コンストラクターの外部に内部クラスを公開します。正しい例は本に記載されています。
プライベートコンストラクター+パブリックエンジニアリングメソッドを介して
/**
* SafeListener
* <p/>
* Using a factory method to prevent the this reference from escaping during construction
*
* @author Brian Goetz and Tim Peierls
*/
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;
}
void doSomething(Event e) {
}
interface EventSource {
void registerListener(EventListener e);
}
interface EventListener {
void onEvent(Event e);
}
interface Event {
}
}
2番目のケース:コンストラクターでスレッドを開始します
①コンストラクタ、スレッドを作成
②コンストラクタでスレッドを開始します
解決:
コンストラクターでスレッドが開始されないコンストラクターがビルドされた後、スレッドは外部で開始されます