コンストラクターでスレッドを開始しないのはなぜですか?
1. 概要
このクイックチュートリアルでは、コンストラクター内でスレッドを開始しない理由を説明します。
まず、JavaとJVMでのパブリケーションの概念を簡単に紹介します。 次に、この概念がスレッドの開始方法にどのように影響するかを確認します。
2. 出版と脱出
現在のスコープ外の他のコードでオブジェクトを利用できるようにするたびに、基本的にそのオブジェクトを公開します。 たとえば、オブジェクトを返すとき、オブジェクトを public 参照に格納するとき、または別のメソッドに渡すときに、公開が行われます。
持つべきではないオブジェクトを公開するとき、そのオブジェクトはエスケープされたと言います。
完全に構築される前にオブジェクトを公開するなど、オブジェクト参照をエスケープさせる方法はたくさんあります。 実際のところ、これはエスケープの一般的な形式の1つです。オブジェクトの構築中にこの参照がエスケープする場合。
この参照が構築中にエスケープされると、他のスレッドはそのオブジェクトが不適切で完全に構築されていない状態であると見なす可能性があります。 これは、次に、奇妙なスレッドセーフの問題を引き起こす可能性があります。
3. スレッドでの脱出
この参照をエスケープさせる最も一般的な方法の1つは、コンストラクターでスレッドを開始することです。これをよりよく理解するために、例を考えてみましょう。
public class LoggerRunnable implements Runnable {
public LoggerRunnable() {
Thread thread = new Thread(this); // this escapes
thread.start();
}
@Override
public void run() {
System.out.println("Started...");
}
}
ここでは、この参照をスレッドコンストラクターに明示的に渡します。 したがって、新しく開始されたスレッドは、完全な構築が完了する前に、囲んでいるオブジェクトを認識できる可能性があります。並行コンテキストでは、これにより微妙なバグが発生する可能性があります。
この参照を暗黙的に渡すことも可能です:
public class ImplicitEscape {
public ImplicitEscape() {
Thread t = new Thread() {
@Override
public void run() {
System.out.println("Started...");
}
};
t.start();
}
}
上に示したように、Threadから派生した匿名の内部クラスを作成しています。 内部クラスはそれを囲むクラスへの参照を維持するため、この参照は再びコンストラクターからエスケープされます。
コンストラクター内にスレッドを作成することには本質的に問題はありません。ただし、すぐに開始することは強くお勧めしません。ほとんどの場合、エスケープされたthisになります。 明示的または暗黙的に参照。
3.1. 代替案
コンストラクター内でスレッドを開始する代わりに、このシナリオ専用のメソッドを宣言できます。
public class SafePublication implements Runnable {
private final Thread thread;
public SafePublication() {
thread = new Thread(this);
}
@Override
public void run() {
System.out.println("Started...");
}
public void start() {
thread.start();
}
};:
上に示したように、私たちはまだ公開していますこれへの参照
SafePublication publication = new SafePublication();
publication.start();
したがって、オブジェクト参照は、完全に構築される前に別のスレッドにエスケープすることはありません。
4. 結論
この簡単なチュートリアルでは、安全なパブリケーションを簡単に紹介した後、コンストラクター内でスレッドを開始しない理由を確認しました。
Javaでの公開とエスケープの詳細については、 Java Concurrency inPracticeの本を参照してください。
いつものように、すべての例はGitHubでから入手できます。