Java設計モードシリーズ-単例モード
4406 ワード
オリジナル文章、転載は出典を表示してください:《Java設計モードシリーズ-単例モード》
一、概説
単一の例とは、単一のインスタンスを指し、1つのクラスインスタンスのみがあり、この単一のインスタンスは人によって制御されるべきではなく、コードによって制限され、単一のインスタンスを強制しなければならない.
単一の例には独自の使用シーンがあり、一般的にはビジネスロジックの上限が複数の例に限定されない場合、例えばカウンタのような存在では、一般的に1つのインスタンスを使用して記録する必要があり、複数の例のカウントが不正確である場合があります.
実は単例は明らかな使用場面で、前に勉強したパターンが使われていない複雑なシーンで、単例を使う必要がある限り、単例を使って、簡単で理解しやすいです.
だから、単例モードに関するポイントはシーンではなく、どのように使用するかだと思います.
二、単例実現
2.1怠け者式
怠け者とは何か.名前の通り、仕事をしないことです.ここでも同義です.怠け者式は、システムのロード時にクラスの単一例を作成するのではなく、インスタンスを初めて使用したときに作成します.
詳細は、次のコードの例を参照してください.
2.2餓漢式
どうしておなかがすいたの?飢えている者は、飢えている者は食べ物を選ばない.しかし、食べ物があれば、必ず急いで食べます.同義:クラスをロードすると、クラスの単一のインスタンスが作成され、クラスに保存されます.
詳細は、次のコードの例を参照してください.
2.3二重ロック機構
二重ロックメカニズムとは?
怠け者式の単例モードを実現するコードにはsynchronizedキーワードを使用してインスタンスを同期取得し、単例の一意性を保証するコードがあるが、上記のコードは実行するたびに同期と判断を行い、間違いなく速度を遅くし、二重ロックメカニズムを使用してこの問題を解決することができる.
上のコードを見て、無言ではないかと思いますが、二重ロックはsynchronizedを2つロックする必要があるのではないでしょうか.
...
実はそうではなくて、ここの二重指の二重判断、ロック単はそのsynchronizedを指して、どうして二重判断を行うのか、実はとても簡単で、第一重判断、もし単例がすでに存在するならば、それでは同期操作を行う必要はなくて、直接この実例に戻って、もし作成しなければ、やっと同期ブロックに入って、同期ブロックの目的は前と同じで、2つの呼び出しが同時に行われることを防止するため、複数のインスタンスが生成され、同期ブロックがあれば、同期ブロックの内容にアクセスできるスレッド呼び出しは毎回1つしかなく、最初のプリエンプトロックの呼び出しがインスタンスを取得すると、このインスタンスが作成され、その後の呼び出しは同期ブロックに入らず、直接第1の再判断で単一の例が返される.2つ目の判断については、個人的には補完的な意味が含まれているような気がします(高い人の高見を期待しています).
補足:ロック内部の第2重空判定の役割については、複数のスレッドがロック位置に到達すると、ロック競合を行い、そのうちの1つのスレッドがロックを取得し、1回目に入るとdlがnullとなり、単例オブジェクトの作成を行い、完了後にロックを解除し、他のスレッドがロックを取得すると空判定にブロックされ、作成した単例オブジェクトに直接戻る.
いずれにしても、二重ロックメカニズムを使用すると、プログラムの実行速度が著しく向上し、毎回同期してロックする必要はありません.
実は私が最も気にしているのはvolatileの使用で、volatileキーワードの意味は:その修飾された変数の値はローカルスレッドにキャッシュされず、その変数に対する読み書きはすべて直接共有メモリを操作して実現し、それによって複数のスレッドがこの変数を正しく処理できることを確保することです.このキーワードは仮想マシンのコード最適化をブロックする可能性があるため、実行効率が高くない可能性があります.そのため、一般的には、二重ロックメカニズムを使用することをお勧めしません.適宜使用するのが正理です.
さらに、volatileを実際に使用する目的は、初期化されていない不完全な単一例のインスタンスが露出し、システムがクラッシュすることを防止することである.単一インスタンスを作成するには、まずメモリ領域を割り当て、メモリ領域の最初のアドレスを参照(ポインタ)に指向し、最後にコンストラクタを呼び出してインスタンスを作成する必要があります.この参照(ポインタ)は2ステップ目でnull以外になるため、3ステップ目が実行されず、本当の単一インスタンスが作成されていない場合、スレッドが最初のチェックでfalseに来ると、不完全なインスタンスが直接返され、システムがクラッシュします.
2.4クラスクラス内クラス方式
餓漢式はクラスロード時にインスタンス化が完了するため、実行速度が遅い場合がありますが、二重ロックメカニズムは?また実行効率が悪いという欠点もありますが、これらの欠点を避ける完璧な方法はありませんか?
クラスレベルの内部クラスを使用して、マルチスレッドのデフォルト同期ロックと組み合わせて、遅延ロードとスレッドセキュリティを実現することもあるようです.
上記のコードのようにクラスクラスクラス内部クラスとは、静的内部クラスであり、この内部クラスとその外部クラスとの間には依存関係がなく、外部クラスをロードする場合、その静的内部クラスを同時にロードすることはなく、呼び出しが発生した場合にのみロードが行われ、ロード時には一例のインスタンスが作成されて戻り、怠惰ロード(遅延ロード)を効果的に実現し、同期の問題については,JVMを用いてスレッドセキュリティを実現する餓漢式と同様の静的初期化器を採用した.
静的初期化器を使用すると、クラスのロード時にクラスのインスタンスが作成されますが、インスタンスの作成を静的内部クラスに明示的に配置すると、外部クラスのロード時にインスタンスの作成を行わないため、遅延ロードとスレッドセキュリティの2つの目的が達成されます.
四、使用
Springで作成したBeanインスタンスは、デフォルトでは単一のパターンで存在します.
一、概説
単一の例とは、単一のインスタンスを指し、1つのクラスインスタンスのみがあり、この単一のインスタンスは人によって制御されるべきではなく、コードによって制限され、単一のインスタンスを強制しなければならない.
単一の例には独自の使用シーンがあり、一般的にはビジネスロジックの上限が複数の例に限定されない場合、例えばカウンタのような存在では、一般的に1つのインスタンスを使用して記録する必要があり、複数の例のカウントが不正確である場合があります.
実は単例は明らかな使用場面で、前に勉強したパターンが使われていない複雑なシーンで、単例を使う必要がある限り、単例を使って、簡単で理解しやすいです.
だから、単例モードに関するポイントはシーンではなく、どのように使用するかだと思います.
二、単例実現
2.1怠け者式
怠け者とは何か.名前の通り、仕事をしないことです.ここでも同義です.怠け者式は、システムのロード時にクラスの単一例を作成するのではなく、インスタンスを初めて使用したときに作成します.
詳細は、次のコードの例を参照してください.
public class LHanDanli {
// ,
// ,
private static LHanDanli dl = null;
// , ,
//
private LHanDanli(){}
// ,
// , ,
// , synchronized
public static synchronized LHanDanli getInstance(){
if(dl == null){
dl = new LHanDanli();
}
return dl;
}
}
2.2餓漢式
どうしておなかがすいたの?飢えている者は、飢えている者は食べ物を選ばない.しかし、食べ物があれば、必ず急いで食べます.同義:クラスをロードすると、クラスの単一のインスタンスが作成され、クラスに保存されます.
詳細は、次のコードの例を参照してください.
public class EHanDanli {
// ,
//
private static EHanDanli dl = new EHanDanli();
// ,
private EHanDanli(){}
// ,
public static EHanDanli getInstance(){
return dl;
}
}
2.3二重ロック機構
二重ロックメカニズムとは?
怠け者式の単例モードを実現するコードにはsynchronizedキーワードを使用してインスタンスを同期取得し、単例の一意性を保証するコードがあるが、上記のコードは実行するたびに同期と判断を行い、間違いなく速度を遅くし、二重ロックメカニズムを使用してこの問題を解決することができる.
public class SLHanDanli {
private static volatile SLHanDanli dl = null;
private SLHanDanli(){}
public static SLHanDanli getInstance(){
if(dl == null){
synchronized (SLHanDanli.class) {
if(dl == null){
dl = new SLHanDanli();
}
}
}
return dl;
}
}
上のコードを見て、無言ではないかと思いますが、二重ロックはsynchronizedを2つロックする必要があるのではないでしょうか.
...
実はそうではなくて、ここの二重指の二重判断、ロック単はそのsynchronizedを指して、どうして二重判断を行うのか、実はとても簡単で、第一重判断、もし単例がすでに存在するならば、それでは同期操作を行う必要はなくて、直接この実例に戻って、もし作成しなければ、やっと同期ブロックに入って、同期ブロックの目的は前と同じで、2つの呼び出しが同時に行われることを防止するため、複数のインスタンスが生成され、同期ブロックがあれば、同期ブロックの内容にアクセスできるスレッド呼び出しは毎回1つしかなく、最初のプリエンプトロックの呼び出しがインスタンスを取得すると、このインスタンスが作成され、その後の呼び出しは同期ブロックに入らず、直接第1の再判断で単一の例が返される.2つ目の判断については、個人的には補完的な意味が含まれているような気がします(高い人の高見を期待しています).
補足:ロック内部の第2重空判定の役割については、複数のスレッドがロック位置に到達すると、ロック競合を行い、そのうちの1つのスレッドがロックを取得し、1回目に入るとdlがnullとなり、単例オブジェクトの作成を行い、完了後にロックを解除し、他のスレッドがロックを取得すると空判定にブロックされ、作成した単例オブジェクトに直接戻る.
いずれにしても、二重ロックメカニズムを使用すると、プログラムの実行速度が著しく向上し、毎回同期してロックする必要はありません.
実は私が最も気にしているのはvolatileの使用で、volatileキーワードの意味は:その修飾された変数の値はローカルスレッドにキャッシュされず、その変数に対する読み書きはすべて直接共有メモリを操作して実現し、それによって複数のスレッドがこの変数を正しく処理できることを確保することです.このキーワードは仮想マシンのコード最適化をブロックする可能性があるため、実行効率が高くない可能性があります.そのため、一般的には、二重ロックメカニズムを使用することをお勧めしません.適宜使用するのが正理です.
さらに、volatileを実際に使用する目的は、初期化されていない不完全な単一例のインスタンスが露出し、システムがクラッシュすることを防止することである.単一インスタンスを作成するには、まずメモリ領域を割り当て、メモリ領域の最初のアドレスを参照(ポインタ)に指向し、最後にコンストラクタを呼び出してインスタンスを作成する必要があります.この参照(ポインタ)は2ステップ目でnull以外になるため、3ステップ目が実行されず、本当の単一インスタンスが作成されていない場合、スレッドが最初のチェックでfalseに来ると、不完全なインスタンスが直接返され、システムがクラッシュします.
2.4クラスクラス内クラス方式
餓漢式はクラスロード時にインスタンス化が完了するため、実行速度が遅い場合がありますが、二重ロックメカニズムは?また実行効率が悪いという欠点もありますが、これらの欠点を避ける完璧な方法はありませんか?
クラスレベルの内部クラスを使用して、マルチスレッドのデフォルト同期ロックと組み合わせて、遅延ロードとスレッドセキュリティを実現することもあるようです.
public class ClassInnerClassDanli {
public static class DanliHolder{
private static ClassInnerClassDanli dl = new ClassInnerClassDanli();
}
private ClassInnerClassDanli(){}
public static ClassInnerClassDanli getInstance(){
return DanliHolder.dl;
}
}
上記のコードのようにクラスクラスクラス内部クラスとは、静的内部クラスであり、この内部クラスとその外部クラスとの間には依存関係がなく、外部クラスをロードする場合、その静的内部クラスを同時にロードすることはなく、呼び出しが発生した場合にのみロードが行われ、ロード時には一例のインスタンスが作成されて戻り、怠惰ロード(遅延ロード)を効果的に実現し、同期の問題については,JVMを用いてスレッドセキュリティを実現する餓漢式と同様の静的初期化器を採用した.
静的初期化器を使用すると、クラスのロード時にクラスのインスタンスが作成されますが、インスタンスの作成を静的内部クラスに明示的に配置すると、外部クラスのロード時にインスタンスの作成を行わないため、遅延ロードとスレッドセキュリティの2つの目的が達成されます.
四、使用
Springで作成したBeanインスタンスは、デフォルトでは単一のパターンで存在します.