1. ハングリースタイルのシングルトンクラス
}
プライベート静的シングルトン インスタンス = new Singleton();
プライベート静的シングルトン getInstance(){
インスタンスを返します。
}
}
特徴: ハングリースタイルの事前インスタンス化。遅延スタイルではマルチスレッドの問題はありませんが、getInstance() を呼び出すかどうかに関係なく、メモリ内にインスタンスが存在します。
2. 内部クラス シングルトン クラス
}
プライベート クラス SingletonHoledr(){
プライベート静的シングルトン インスタンス = new Singleton();
}
プライベート静的シングルトン getInstance(){
SingletonHoledr.instance を返します。
}
}
特徴: 内部クラスでは、遅延ロードが実装されています。getInstance() を呼び出した場合にのみ、メモリ内に一意のインスタンスが作成されます。また、遅延スタイルでのマルチスレッドの問題も解決します。 。
3. 遅延シングルトン クラス
}
プライベート静的シングルトン インスタンス。
パブリック静的シングルトン getInstance(){
if(インスタンス == null){
インスタンスを返す = 新しい Singleton();
}それ以外{
インスタンスを返します。
}
}
}
特徴: 遅延スタイルでは、スレッド A と B があります。スレッド A が 8 行目まで実行されると、スレッド B にジャンプします。B も 8 行目まで実行されると、両方のスレッドのインスタンスが空になるため、2 つの例が生成されます。 。解決策は以下を同期することです。
同期は可能ですが、効率的ではありません。
}
プライベート静的シングルトン インスタンス。
public static synchronized Singleton getInstance(){
if(インスタンス == null){
インスタンスを返す = 新しい Singleton();
}それ以外{
インスタンスを返します。
}
}
}
getInstance 全体が「クリティカル セクション」全体であるため、このようなプログラムを作成してもエラーは発生しませんが、実際の目的はインスタンスを初めて初期化するときにロックすることだけであるため、効率は非常に悪いです。 get the インスタンスを使用する場合、スレッドの同期はまったく必要ありません。
そこで賢い人たちは次のようなアプローチを思いつきました。
ダブルチェックロックの書き込み方法:
public static Singleton getSingle(){ //外部オブジェクトはこのメソッドを通じて取得できます
if(単一 == null){
synchronized (Singleton.class) { //この同期ブロックに同時にアクセスできるのは 1 つのオブジェクトだけであることが保証されます
if(単一 == null){
シングル = 新しいシングルトン();
}
}
}
return single // 作成されたオブジェクトを返します。
}
}
考え方は非常にシンプルです。つまり、コードが正しく効率的になるように、インスタンスを初期化するコードの部分を同期 (同期) するだけで済みます。
これは、いわゆる「ダブルチェックロック」機構です(名前が示すとおり)。
残念ながら、この書き方は多くのプラットフォームや最適化コンパイラでは間違っています。
その理由は、さまざまなコンパイラでのコード行の instance = new Singleton() の動作が予測できないためです。最適化コンパイラは、次のように、instance = new Singleton() を合法的に実装できます。
1. インスタンス = 新しいエンティティにメモリを割り当てる
2. Singleton コンストラクターを呼び出してインスタンスのメンバー変数を初期化します。
ここで、スレッド A と B が getInstance を呼び出しているとします。スレッド A が最初に入り、ステップ 1 が実行されると CPU から追い出されます。次に、スレッド B が入ります。B は、インスタンスが null ではなくなった (メモリが割り当てられた) ことを確認します。そのため、スレッド B は自信を持ってインスタンスを使用し始めますが、この時点ではインスタンスのメンバー変数がまだデフォルトのままであるため、これは誤りです。値がある場合、A にはステップ 2 を実行してインスタンスの初期化を完了する時間がまだありません。
もちろん、コンパイラは次のように実装することもできます。
1. temp = メモリの割り当て
2. temp のコンストラクターを呼び出す
3. インスタンス = 温度
コンパイラがこのように動作する場合は問題がないように見えますが、この問題は Java のメモリ モデルで定義されていないため、特定のコンパイラがどのように動作するかを知ることができないため、実際はそれほど単純ではありません。
二重チェック ロックは、基本型 (int など) に適用できます。基本型はコンストラクターを呼び出さないため、当然です。