15

私は、Effective Java(第2版)の項目71「怠惰な初期化を慎重に使用する」を進めています。このコード(283ページ)を使用して、インスタンスフィールドの遅延初期化にダブルチェックイディオムを使用することをお勧めします。

private volatile FieldType field;
FieldType getField() {
    FieldType result = field;
    if (result == null) {  //First check (no locking)
        synchronized(this) {
            result = field;
            if (result == null)  //Second check (with locking)
                 field = result = computeFieldValue();
        }
     }
     return result;
}

だから、私は実際にいくつかの質問があります:

  1. field同期ブロックで初期化が行われるのに、なぜ揮発性修飾子が必要なのですか?この本は、このサポートテキストを提供しています。「フィールドがすでに初期化されている場合はロックがないため、フィールドが揮発性であると宣言することが重要です」。したがって、フィールドが初期化されるとfield、他の同期がない場合に、複数のスレッドで一貫したビューが保証されるのはvolatileだけですか?もしそうなら、なぜgetField()を同期しないのですか、それとも上記のコードがより良いパフォーマンスを提供するのですか?

  2. このテキストは、不要なローカル変数、resultを使用して、「fieldすでに初期化されている一般的なケースで1回だけ読み取られるようにする」ことで、パフォーマンスを向上させることを示唆しています。削除された場合、すでに初期化されている一般的なケースでresultは、どのようfieldに複数回読み取られますか?

4

3 に答える 3

16

Why is the volatile modifier required on field given that initialization takes place in a synchronized block?

The volatile is necessary because of the possible reordering of instructions around the construction of objects. The Java memory model states that the real-time compiler has the option to reorder instructions to move field initialization outside of an object constructor.

This means that thread-1 can initialized the field inside of a synchronized but that thread-2 may see the object not fully initialized. Any non-final fields do not have to be initialized before the object has been assigned to the field. The volatile keyword ensures that field as been fully initialized before it is accessed.

This is an example of the famous "double check locking" bug.

If result was removed, how would field be read multiple times in the common case where it was already initialized?

Anytime you access a volatile field, it causes a memory-barrier to be crossed. This can be expensive compared to accessing a normal field. Copying a volatile field into a local variable is a common pattern if it is to be accessed in any way multiple times in the same method.

See my answer here for more examples of the perils of sharing an object without memory-barriers between threads:

About reference to object before object's constructor is finished

于 2013-02-17T23:07:04.997 に答える
7

これはかなり複雑ですが、コンパイラが物事を再配置できるようになったことに関連しています。
基本的にDouble Checked Locking、変数が。でない限り、パターンはJavaでは機能しませんvolatile

これは、場合によっては、コンパイラが変数を割り当てることができるため、null以外の何かが変数の初期化を実行し、それを再割り当てするためです。別のスレッドは、変数がnullでないことを確認し、それを読み取ろうとします。これにより、あらゆる種類の非常に特殊な結果が発生する可能性があります。

このトピックに関するこの他のSOの質問を見てください。

于 2013-02-17T23:09:44.897 に答える
3

良い質問です。

初期化が同期ブロックで行われるのに、フィールドに volatile 修飾子が必要なのはなぜですか?

同期がなく、その共有グローバル フィールドに割り当てた場合、そのオブジェクトの構築時に発生するすべての書き込みが表示されるという保証はありません。たとえば、FieldType が次のように見えると想像してください。

public class FieldType{
   Object obj = new Object();
   Object obj2 = new Object();
   public Object getObject(){return obj;}
   public Object getObject2(){return obj2;}
}

null 以外のインスタンスを返すことは可能ですgetField()が、そのインスタンスgetObj()getObj2()メソッドは null 値を返す可能性があります。これは、同期がないと、これらのフィールドへの書き込みがオブジェクトの構築と競合する可能性があるためです。

これは揮発性でどのように修正されますか? 揮発性書き込みの前に発生したすべての書き込みは、揮発性書き込みが発生した後に表示されます。

結果が削除された場合、フィールドが既に初期化されている一般的なケースで、フィールドを複数回読み取るにはどうすればよいですか?

一度ローカルに保存し、メソッド全体で読み取ると、1 つのスレッド/プロセス ローカル ストアとすべてのスレッド ローカル読み取りが保証されます。それらの点で時期尚早の最適化を主張することができますが、そうしないと発生する可能性のある奇妙な並べ替えの問題に遭遇しないため、私はこのスタイルが好きです。

于 2013-02-17T23:27:25.010 に答える