2012-05-22 9 views
7

この件に関する質問がいくつかありますが、問題の意図ではないため、この問題の周りにはほとんどのスカートがあります。静的な揮発性変数を同期する必要がありますか?

私は私のクラスに静的な揮発性を持っている場合:

private static volatile MyObj obj = null; 

とI以下の方法で行います。

public MyObj getMyObj() { 
    if (obj == null) { 
     obj = new MyObj();// costly initialisation 
    } 
    return obj; 
} 

は、私は一つのスレッドだけがフィールドへの書き込みを保証するために同期させる必要がありますまたは、任意の書き込みがobj == null条件を評価する他のスレッドにすぐに見えるようになりますか?

別の言い方をすると、volatileは静的変数の書き込みへのアクセスを同期させる必要がありますか?

答えて

8

一部のスレッドのみがフィールドに確実に書き込むようにするには、ある種のロックが必要です。ボラティリティにかかわらず、2つのスレッドはobjがヌルであることを両方見ることができ、両方とも現在のコードで初期化を開始します。

  • 初期クラスの負荷に(それは怠惰になることを知って、しかしとして怠惰ではないgetMyObjまで待っが最初に呼び出されると):

    個人的に私は3つのオプションのいずれかが取ると思い

    private static final MyObj obj = new MyObj(); 
    
  • 利用無条件ロック:

    private static MyObj obj; 
    private static final Object objLock = new Object(); 
    
    public static MyObj getMyObj() { 
        synchronized(objLock) { 
         if (obj == null) { 
          obj = new MyObj(); 
         } 
         return obj; 
        } 
    } 
    
  • 使用そのように怠惰のためのネストされたクラス:

    public static MyObj getMyObj() { 
        return MyObjHolder.obj; 
    } 
    
    private static class MyObjHolder { 
        static final MyObj obj = new MyObj(); 
    } 
    
+0

私はホルダールートに行くことができません:私は開発している全体的なフレームワークにはるかに多くの思考と変更を必要とする私の外部クラスのシングルトンを強制します。私が解決する必要があった問題は、静的インスタンスのアトミックな作成でした。私は静的メソッドを使用し、それを宣言した( 'obj')スタティックfinalで原子の割り当てと並行安全を可能にしました。 –

+0

@atc:いいえ。ホルダールートでは、外部クラスを強制的にシングルトンにすることはありません。ここにあなたが意味することは本当に明確ではありません... –

+0

明快にするために、この 'static volatile'変数はもともとオブジェクトファクトリにあり、実行時にそれを作成するインスタンスに注入しました。これは、前述のstaic var(上記の 'MyObj')の実装の詳細によるものです。ホルダーイディオムを満たすための 'private'コンストラクターの使用は、オブジェクトファクトリの継承の利点を保てないということを意味しました。ここでホルダーイディオムを卑劣にするのではなく、コードベースとの摩擦が最小で、動作することが証明されているので、私はダブルチェック同期ルートに行くと思った。 –

3

はい、絶対に同期してください(またはSingleton Holder idiomのような良いイディオムを使用する必要があります)。そうしないと、複数のスレッドがオブジェクトを何度も初期化するリスクがあります(その後、異なるインスタンスを使用する)。

は、このような一連のイベントを考えてみましょう:

  1. スレッドAはgetMyObj()に入り、obj == null
  2. スレッドBがgetMyObj()に入り、obj == null
  3. スレッドAがnew MyObj()を構築することを認識していることを見ている - のはobjAそれを呼びましょう
  4. スレッドBは、new MyObj()を構成します - objB
  5. スレッドAは、スレッドAがgetMyObj()と開始終了
  6. (この時点ではもはやnullないので、スレッドAにより割り当てobjAへの参照は、上書きされる)objA
  7. objにスレッドBが objobjBを割り当て、割り当て objA
  8. はスレッドBを使用することがgetMyObj()を終了し、objB

このシナリオの使用を開始します任意の数のスレッドで発生する可能性があります。ここでは単純化のために、厳密なイベントの順序付けを前提としていましたが、実際のマルチスレッド環境のイベント1-2,3-4および/または7-8は、時間を部分的または完全にオーバーラップさせることができます。結果。ホルダーのイディオムに

例:INSTANCEfinalあるよう

public class Something { 
    private Something() { 
    } 

    private static class LazyHolder { 
     public static final Something INSTANCE = new Something(); 
    } 

    public static Something getInstance() { 
     return LazyHolder.INSTANCE; 
    } 
} 

はこれは、安全であることが保証されます。 Javaメモリモデルは、包含するクラスをロードするときにfinalフィールドが初期化され、任意の数のスレッドに正しく表示されることを保証します。 LazyHolderprivateであり、getInstance()によってのみ参照されます。getInstance()が最初に呼び出されたときにのみ読み込まれます。その時点で、INSTANCEはバックグラウンドで初期化され、JVMによって安全に公開されます。

+0

フィールドを静的にし、静的初期化子または静的メソッドを使用して作成を行うことができます。同期化および試行/キャッチロジックが可能です。こうすることで読みやすくなります+静的初期化子はクラスが利用可能になる前に終了するので、 'getMyObj()'コールをロックする必要はありません。 –

+0

@atc、あなたの説明はあいまいなので、コードサンプルなしでスレッドの安全性と実行可能性を評価することはできません。ホルダーイディオムはロックも使用しないことに注意してください(JVMは 'LazyHolder'の構築をロックしますが、' getInstance() '自体は同期しません)。 –

0

コードはスレッドセーフではありません。複数のスレッドが関数を実行する場合、MyObjの複数のインスタンスを作成できます。ここには何らかの形の同期が必要です。

根本的な問題は、このブロックのコードということである。

if (obj == null) { 
    obj = new MyObj();// costly initialisation 
} 

がアトミックではありません。実際、それは原子的であることから非常に遠い方法です。

1

いいえ、あなたはまだアクセスを同期する必要があります。 volatileは、あるスレッドが変数に行った変更を他のスレッドが見ることを可能にします。

は(二つのスレッドT1 & T2と仮定して)実行の次のフローを想像:

  1. objがinitallyヌルです。
  2. T1:(OBJ == NULL)場合:
  3. YES T2:(OBJ == NULL)場合:YES
  4. T1:このmyobjの新しいインスタンスを作成し、
  5. T2をobjにそれを割り当てる。またMyObjの新しいインスタンスを作成してobjに割り当てます

作成するオブジェクトを2回作成するのは1回だけです。これは悪いシナリオではありません。変数にもはや割り当てられていないオブジェクトを返すことになります。これを処理する

0

さらにもう1つの方法は、(:Javaの5以降でのみ動作し、詳細についてはhereを参照してください。注):ダブルチェックです

public class DoubleCheckingSingletonExample 
{ 
    private static final Object lockObj = new Object(); 

    private static volatile DoubleCheckingSingletonExample instance; 

    private DoubleCheckingSingletonExample() 
    { 
     //Initialization 
    } 

    public static DoubleCheckingSingletonExample getInstance() 
    { 
     if(instance == null) 
     { 
      synchronized(lockObj) 
      { 
       if(instance == null) 
       { 
        instance = new DoubleCheckingSingletonExample(); 
       } 
      } 
     } 

     return instance; 
    } 
} 

のgetInstanceの両方が最初になります、同時に二つのスレッドから呼び出された場合そのインスタンスをヌルとして参照し、もう1つはsynchronized-blockに入り、オブジェクトをインスタンス化します。もう一方のインスタンスはインスタンスがnullではなく、インスタンス化しようとしません。さらに、getInstanceを呼び出すと、インスタンスがnullではないことがわかり、まったくロックしようとしません。

+0

このような二重チェックロックでは、正しく動作するためにJDK5が必要です。http://cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html –

+2

申し訳ありませんが、正しいリンクはhttp://www.csです。 umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html –

+0

@DavidHeffernan:リンクありがとうございました。「JDK5以上」の要件について知らなかった。私はこの情報を含める答えを編集します。 – esaj

関連する問題