怠惰な初期化のリスク(例外で現在支払っている)を排除することで、混乱の一部を回避できます。実際には静的インスタンスを返すだけなので、実行時にその静的インスタンスを作成しないのはなぜですか(つまり、nullを待たないでください)。
class MySingleton {
private static MySingleton instance = new MySingleton();
// You could do this here or in the constructor
// private OtherObject obj = new OtherObject();
/** Use this if you want to do it in the constructor instead. */
private OtherObject obj;
private MySingleton() {
obj = new OtherObject();
}
/** Now you can just return your static reference */
public static MySingleton getInstance() {
return instance;
}
}
お気づきの方もいらっしゃると思いますが、今ではすべてが決定論的で簡単です。MySingleton.instanceは実行時に入力され、静的メソッドを介してアクセスされますMySingleton.getInstance()
。
これは元のGOFデザインパターンブックの正確なパターンとは一致しないことはわかっていますが、クラスの使用法は事実上同じであることに気付くでしょう。
編集:他の回答やコメントで提起されたスレッドセーフポイントのいくつかをフォローアップして、質問とGOFブックで提案された元のソリューションがスレッドセーフではないことを説明しようと思います。より適切なリファレンスについては、私が所有し、ACM/Safariオンライン本棚にあるJavaConcurrencyinPracticeを参照してください。率直に言って、私のスケッチ例よりも優れた情報源ですが、ねえ、私たちは努力するしかありません...。
したがって、Thread1とThread2という名前の2つのスレッドがあり、偶然にも、それぞれが同時にMySingleton.getInstance()メソッドにヒットするとします。これは、最新のマルチコアハイパースレッドシステムで完全に可能です。現在、これら2つのスレッドがたまたまこのメソッドのエントリポイントにヒットしたとしても、どちらが特定のステートメントにヒットするかは保証されません。したがって、次のようなものが表示されます。
// Note that instance == null as we've never called the lazy getInstance() before.
Thread1: if (instance == null)
Thread2: if (instance == null) // both tests pass - DANGER
Thread1: instance = new MySingleton();
Thread2: instance = new MySingleton(); // Note - you just called the constructor twice
Thread1: return instance; // Two singletons were actually created
Thread2: return instance; // Any side-effects in the constructor were called twice
if-nullテストで示されている問題は、競合状態と呼ばれます。誰がいつどのステートメントに行くのかわかりません。結果として、それは両方の糸が崖に向かって互いに競争しているようです。
両方のスレッドが同時にgetInstance()をヒットしている状態で、私の例の同じ種類の検査を見ると、次のようになります。
Thread1: return instance;
Thread2: return instance;
単純です、はい、しかしそれが私のポイントです。オブジェクトはずっと前に一度構築されたものであり、スレッドはその値が一貫していることを期待できます。人種は存在しません。
注:OtherObjectのコンストラクターの内容についてはまだ心配する必要があります。そこにある教訓:並行性は難しい。コンストラクタースレッドを安全にする場合(そうする必要があります)、OtherObjectの作成者が同じ礼儀を行っていることを確認してください。