2011-02-07 8 views
14

フィールドの割り当ては、longまたはdoubleのフィールドを除いて常にアトムであると言われています。Javaでダブルチェックのロックが解除されるのはなぜですか?

しかし、私はダブルチェックロックが壊れている理由の、について説明を読んだとき、問題が代入演算であると言われています:値が初期化されていない

// Broken multithreaded version 
// "Double-Checked Locking" idiom 
class Foo { 
    private Helper helper = null; 
    public Helper getHelper() { 
     if (helper == null) { 
      synchronized(this) { 
       if (helper == null) { 
        helper = new Helper(); 
       } 
      } 
     } 
     return helper; 
    } 

    // other functions and members... 
} 
  1. スレッドAの通知したがって、 ロックを取得し、 値の初期化を開始します。
  2. 何らかのプログラミング言語の意味論に、コンパイラによって生成されたコードは Aが初期化を実行完了する前に、部分的に構築されたオブジェクトに を指すように、共有変数を更新することができます。
  3. スレッドBは、共有変数が初期化されていることに気付き(または と表示されます)、その値を返します。 スレッドBは、既に値が であると考えているので、 はロックを取得しません。 BがAによって を行って、初期化のすべての前にオブジェクト を使用している場合は初期化された値のいくつかは、オブジェクト内の がまだメモリBに を浸透していないため がそれを初期化したり が完了していないので、どちらか(Bで見られています使用(キャッシュ 一貫性))、プログラムはおそらく クラッシュします。
    http://en.wikipedia.org/wiki/Double-checked_lockingから)。

いつ可能ですか? 64ビットJVMの割り当て操作がアトミックでない可能性はありますか? 「ダブルチェックロック」が本当に壊れていないかどうかを確認します。

+1

64ビットJVMでは違いはありません。 – rapadura

+0

可能な複製http://stackoverflow.com/questions/12448864/java-double-locking-can-someone-explain-more-simply-why-intuition-wouldnt-wor/12449110?noredirect=1#comment26446551_12449110 –

答えて

15

問題は原子性ではなく、順序です。 happens-beforeに違反していない限り、JVMはパフォーマンスを向上させるために命令を並べ替えることができます。したがって、ランタイムは、Helperクラスのコンストラクタのすべての命令が実行される前に、helperを更新する命令を理論的にスケジュールできます。

+0

それはいつも注文している、そうではありません:)。それは正解です – bestsss

2

Helperのインスタンスをコンストラクタ内に構築するには、いくつかの割り当てが必要な場合があり、セマンティクスでは、割り当てに関して並べ替えることができますhelper = new Helper()

フィールドhelperには、すべての割り当てが行われていないオブジェクトへの参照が割り当てられているため、不完全に初期化されている可能性があります。

+0

何をするあなたは「複数の課題」を意味しますか? 'helper = new Helper()'のみが表示されます。それが "適切な場所に"構築できれば、あなたは本当にJavaがとても変わったことを証明するリンクを与えることができますか?どうも。 – Roman

+1

@Roman:あなたの質問の引用符で述べたように、コンパイラによって生成されたコードは、 'helper'のインスタンスに' Helper'を割り当てできます。 – ColinD

7

参照の割り当ては、原子ですが、構造はありません!スレッドAがスレッドAを完全に構築する前にスレッドBがシングルトンを使いたいと仮定すると、参照がヌルではないため新しいインスタンスを作成することはできないため、部分的に構築されたオブジェクトを返します。

あなたは共有参照が 前 参照を共有し、他のスレッドの負荷をたまたま を公開することは、その後、新しいオブジェクトへの 参照の書き込みがその フィールドへの書き込みで を並べ替えることができることを確認していない場合。この場合、別のスレッド は、オブジェクトの オブジェクトの最新の値を見ることができますが、オブジェクトの 状態の一部またはすべての部分の値が です。部分的に構築された オブジェクトです。 - Brian Goetz:Javaの同時実行性

nullの初期チェックは同期化されていないため、この並べ替えは可能です。

+0

javaは割り当てを最初に行い、次に正しい部分を計算するだけです。 (これは 'Future'パターンのようなものですが、Javaコンパイラの使用法についての公式声明は見たことがありません)。 – Roman

+0

@Roman:「Javaは最初に割り当て、次に正しい部分を計算する」という単純な問題ではありません。それは私が思うには間違った声明でしょう。 Javaでは、低レベルのコードでは、「volatile」や「final」以外の共有フィールドに別のスレッドがアクセスしたときに予期しない結果を引き起こす可能性のある方法で文を並べ替えることができます。 – ColinD

+0

@Roman更新された回答を参照してください... – Robert

0

ご迷惑をおかけして申し訳ございませんが、質問には関係ありません。興味があります。 この場合、代入の前にロックを取得したり、値を返す方が良いでしょうか? Like:

private Lock mLock = new ReentrantLock(); 
private Helper mHelper = null; 

private Helper getHelper() { 
    mLock.lock(); 
    try { 
     if (mHelper == null) { 
      mHelper = new Helper(); 
     } 
     return mHelper; 
    } 
    finally { 
     mLock.unlock(); 
    } 
} 

また、ダブルチェックロックを使用する利点はありますか?

-1
/*Then the following should work. 
    Remember: getHelper() is usually called many times, it is BAD 
    to call synchronized() every time for such a trivial thing! 
*/ 
class Foo { 

private Helper helper = null; 
private Boolean isHelperInstantiated; 
public Helper getHelper() { 
    if (!isHelperInstantiated) { 
     synchronized(this) { 
      if (helper == null) { 
       helper = new Helper(); 
       isHelperInstantiated = true; 
      } 
     } 
    } 
    return helper; 
} 

// other functions and members... 
}  
関連する問題