最初にコードを与えてください:
class Cat {
private String name;
//volatile 能够防止指令重排造成的问题
private volatile static Cat cat; //声明唯一实例
private Cat(String name) {
this.name = name;
}
public static Cat getInstance() {
if (cat == null) {
//保证加锁之前只有一个对象
synchronized (Cat.class) {
//获取锁
if (cat == null) {
cat = new Cat("DCL懒汉猫");
}
}
}
return cat;
}
}
シングルトンパターンの特徴:
- シングルトン クラスはインスタンス オブジェクトを 1 つだけ持つことができます。
- シングルトン クラスは、このインスタンス自体を作成する必要があります。
- シングルトン クラスは、インスタンスを取得するためのメソッド getInstance() を提供する必要があります。
オブジェクトが null かどうかを 2 回判定するのは何のためですか?
一番外側の if 判定は分かりやすく、オブジェクトが空ではなくなり、作成されていれば、ロックを取得することを気にする必要はなく、インスタンスをそのまま返すだけです。
では、なぜロックを取得した後にさらに判断層を追加する必要があるのでしょうか? この状況を考えてみましょう:
2 つのスレッド A と B がオブジェクトを取得します。この時点では、オブジェクトは作成されていません。A と B はどちらも最初のレベルの判定を通過しています。2 つのスレッドはロックをめぐって競合し始め、A はロックを取得します。したがって、B は同期キューに入り、ブロックされて待機します。**A がオブジェクトを作成し、ロックを解放すると、B はロックを取得し、新しい Cat (「DCL Lazy Cat」) を実行します。このとき、2 つのオブジェクトが作成されます。** したがって、オブジェクトが 1 回だけ作成されるようにロック後に判断する必要があります。
なぜ volatile キーワードをインスタンス オブジェクトに追加するのでしょうか?
これは、コンパイラがコンパイル中に命令の再配置を実行し、volatile が命令の再配置を禁止できるためです。
オブジェクトを作成するには、およそ 3 つのステップがあります (命令レベルでは、JVM の観点からは多くのステップがあります)。
- メモリ空間を割り当てる
- コンストラクターメソッドを実行し、オブジェクトを初期化します。
- このオブジェクトをこのスペースに向けます
通常は 123 の順番で実行されますが、命令の並び替えの存在により、スレッド A が 132 の順番で実行される場合があります。実行が 3 に達すると、スレッド B がオブジェクトをフェッチしに来て null 値を取得します。したがって、volatile キーワードをシングルトン オブジェクトに追加する必要があります。
やっと
実際、DCL 遅延スタイルは Java では絶対に安全というわけではなく、リフレクション領域を通じて破壊される可能性があります。列挙型クラスを使用すると、絶対に安全なシングルトン パターンを作成できます。列挙型クラスは、リフレクションを防ぐだけでなく、オブジェクトを作成するための逆シリアル化も防ぎます。