よると実践でのJava並行処理、クラスのコンストラクタ内のスレッドを開始することは危険です。その理由は、これが公開されるということであるthis
オブジェクトが完全に構築される前に、別のスレッドへのポインタを。
多くの以前のStackOverflowの質問に議論されているこのトピックにもかかわらず、私はまだ難しい、これは、そのような懸念である理由を理解を持っています。特に、私は、コンストラクタ内でスレッドを開始すると、Javaのメモリモデルの観点からメモリの一貫性の問題につながることができるかどうかについて明確さを追求したいと考えています。
私はあなたに私がやりたい事の種類の具体的な例を挙げてみましょう。(数20がコンソールに印刷するためのコードのこの部分のための所望の出力です。)
private static class ValueHolder {
private int value;
private Thread thread;
ValueHolder() {
this.value = 10;
thread = new Thread(new DoublingTask(this)); // exposing "this" pointer!!!
thread.start(); // starting thread inside constructor!!!
}
int getValue() {
return value;
}
void awaitTermination() {
try {
thread.join();
} catch (InterruptedException ex) {}
}
}
private static class DoublingTask implements Runnable {
private ValueHolder valueHolder;
DoublingTask(ValueHolder valueHolder) {
this.valueHolder = valueHolder;
}
public void run() {
System.out.println(Thread.currentThread().getName());
System.out.println(valueHolder.getValue() * 2); // I expect to print out 20...
}
}
public static void main(String[] args) {
ValueHolder myValueHolder = new ValueHolder();
myValueHolder.awaitTermination();
}
はい、私たちは、コンストラクタから戻る前にスレッドが開始されていることを知っています。はい、私はことを知っているthis
ポインタがスレッドにさらされています。そして、まだ、私はコードが正しいことを確信しています。私はそれが常にコンソールに数20を印刷すると確信しています。
- 割り当てが
this.value = 10
起こる-前thread.start()
。(これは、あるthis.value = 10
先行thread.start()
プログラムの順序インチ) - 呼び出し
thread.start()
メインスレッドでは、たまたま、前の開始.run()
新しく作成されたスレッドの方法。(そのためthread.start()
、同期アクションです。) - 開始
.run()
方法は起こる-前にSystem.out.println(valueHolder.getValue() * 2);
print文。(ここでも、プログラムの順序によって)。
そのため、Javaのメモリモデルで、print文がの正しく初期化値を読んでくださいvalueHolder.value
(つまり、10)。だから、からの助言を無視したにも関わらず、実際にはJavaの並行処理、私はまだコードの正しい部分を書かれているように見えます。
私がミスをしたことがありますか?私は何をしないのですか?
UPDATE:回答やコメントに基づいて、私は今、私のコード例があることを理解している私が提供する理由のために機能的に正しいです。他の開発者が将来スレッドの開始後、さらに初期化ステートメントを追加する可能性があるのでしかし、それはこのように書き込みコードに悪い習慣です。このクラスのサブクラスを実装する際に、このようなエラーは可能性のある一つの状況があります。
私はあなたのクラスをサブクラス化したとします。それは彼らが必要としている時間によって、そのフィールドを初期化していない可能性があります。
class BetterValueHolder extends ValueHolder
{
private int betterValue;
BetterValueHolder(final int betterValue)
{
// not actually required, it's added implicitly anyway.
// just to demonstrate where your constructor is called from
super();
try
{
Thread.sleep(1000); // just to demonstrate the race condition more clearly
}
catch (InterruptedException e) {}
this.betterValue = betterValue;
}
@Override
public int getValue()
{
return betterValue;
}
}
これは関係なく、コンストラクタに与えられているどのような値のゼロを出力しますBetterValueHolder
。