1
1.   class Foo {
2.       private Helper helper = null;
3.       public Helper getHelper() {
4.           if (helper == null) {
5.              synchronized(this) {
6.                 if (helper == null) {
7.                    helper = new Helper();
8.                 }
9.              }
10.          }
11.          return helper;
12.      }
13.  }

この構造が壊れていると見なされる理由は、一般に、ヘルパー変数への書き込み後にヘルパー コンストラクターが呼び出されるように、コンパイラによって行われる割り当ての並べ替えが説明されています。私の質問は、このコードがどのようにスレッドセーフであり、次の手順が可能かということです。

  • スレッド 1、同期ブロックに入り、ヘルパーが null であることを確認します。
  • スレッド 1、この時点でモニターをあきらめます
  • スレッド 2、オブジェクト モニターに入り、ヘルパーをインスタンス化します。
  • スレッド 1 が戻ってきて、ヘルパー インスタンスを次のように再初期化します。

このソリューションが単一のチェック付きロックよりも優れているとは思いません。

4

3 に答える 3

4

これはヘルパーへの参照には機能しますが、まだ微妙に壊れています。

VM は同期ブロックのプログラム アクションを好きなだけ並べ替えることができるため、ヘルパー インスタンスの構築 (スレッド 1 による) が完了する前に参照ヘルパーを非 null に設定できるため、壊れています。

スレッド 2 は、同期ブロックの外側で非 null を認識できるようになり、同期ブロックに入ろうとはしません (スレッド 1 はまだロックを保持しており、ヘルパーの構築で忙しくなります)、半分構築されたヘルパー インスタンスで動作します。

これは、特定の VM バージョンで発生する場合と発生しない場合があります。ただし、仕様では、VM がこれを行うことを明示的に許可しています。そのため、例が壊れています。ヘルパー volatile を宣言することで修正できます (Java 5+ のみ)。

于 2014-07-17T18:40:02.630 に答える