no_mkd
一.シングルトンパターンとは何か?
シングルトンパターンはシングルトンパターンとも呼ばれ、その目的は端的に言えば「そのクラスのインスタンスが 1 つだけ存在することを保証する」ことです
シングルトンパターンはリソースに敏感なオブジェクトを管理するためによく使用されます。例えば:データベース接続オブジェクト、レジストリオブジェクト、スレッドプールオブジェクトなどです。このようなオブジェクトが同時に複数存在すると、さまざまな不整合な問題を引き起こすことになります(データベースの重複接続エラーが発生することを望まないでしょう)
二.クラスのインスタンスが 1 つだけであることをどのように保証するか?
(この問題はシンプルに見えますが、シングルトンパターンに触れたことがな���れば、解決策を自分で考え出すにはある程度の才能が必要です。信じられないなら、考えてみてください。。)
1.クラスのインスタンスは 1 つだけになる可能性がありますか?クラス名を知っていれば自由に new できるのではないでしょうか?
もちろんできます。クラス名を知っていれば、そのコンストラクタを呼び出して new することができます。しかし、一点見落としています:そのクラスにはパブリックなコンストラクタが必要です。不是吗?
2.つまり、クラスのインスタンスを 1 つだけ保証するには、そのクラスにパブリックなコンストラクタがあってはならない、对吧?
その通りです。プライベートコンストラクタを定義する必要があります
3.パブリックコンストラクタのないクラスはインスタンスを生成できますか?コンストラクタが private の場合、そのクラスのインスタンスのみがこのコンストラクタを呼び出せます。同様に、このコンストラクタを呼び出さなければそのクラスのインスタンスを生成できません。。これは「鶏が卵を産み、卵が鶏を産む。。」問題ではありませんか?プライベートコンストラクタでももちろんインスタンスを生成できます。上で見落とした一点があります:「そのクラスのインスタンスのみがこのコンストラクタを呼び出せる」わけではありません。クラス内部であれば、インスタンスを生成しなくても自由にこのプライベートコンストラクタを呼び出せるからです
上記の議論結果があれば、クラシックなシングルトンパターンを実装できます:
package SingletonPattern;/**
-
@author ayqy
-
最もクラシックなシングルトンパターン */ public class Singleton {
private static Singleton instance;//静的インスタンス変数を定義
/**
- プライベートコンストラクタを定義し、外部からの new を防止 */ private Singleton(){ //初期化処理 }
/**
- グローバルアクセスポイントを提供
- @return クラスのインスタンス */ public static Singleton getInstance(){ if(instance == null) instance = new Singleton(); return instance; }
/*
- その他有用なプロパティと動作
- シングルトンパターンを適用したクラスでも本来の機能を保持する
- */ }
注意:最後に一点を明確に理解してください。シングルトンパターンを適用したクラスは本来の機能を失ってはなりません。使用するために使用してはいけません
三.シングルトンパターンをさらに考える
私たちのシングルトンパターンは完全に安全でしょうか?いいえ、まだ多くの問題があります。例えば:
1.マルチスレッド環境下;2.複数の class loader 環境下
生成されるインスタンスが 1 つだけであることを保証できません,对吧?
しかし、成熟したデザインパターンとして、シングルトンパターンはこれらの環境に対応できなければなりません。したがって、次にこれらの環境にどのように対応するかを議論します
四.マルチスレッド環境下のシングルトンパターン
マルチスレッド環境下でインスタンスの一意性をどのように保証するか?synchronized キーワードを使用してスレッドセーフを確保することを容易に考えつきます。以下のようになります:
/**
* グローバルアクセスポイントを提供
* @return クラスのインスタンス
*/
public static synchronized Singleton getInstance(){
if(instance == null)
instance = new Singleton();
return instance;
}
getInstance メソッドを同期メソッドとして定義することで、複数のスレッドが同時にこのメソッドに入ることを防ぎ、異なるインスタンスが生成されることを防ぎます
しかし、上記の方法には致命的な問題があります:synchronized キーワードでメソッドを同期すると、効率が大幅に低下します(メソッド 1 つを同期すると効率百倍の低下を引き起こす可能性があります。。)。これではプログラムが拖累されてしまいます。より良い改善方法はあるでしょうか?
まず、上記の同期ブロックは getIntance メソッド全体です。このメソッドを呼び出すたびに強制的に同期機構に入りますが、よく考えると、このメソッドを初めて呼び出すときのみ同期が必要です(最初のオブジェクト new)。その後の呼び出しでは new 済みのオブジェクトを返すだけでよいのです
したがって、改善案は:ダブルロックを使用して、最初のオブジェクト new 時のみ同期を行うことです
package SingletonPattern;/**
-
@author ayqy
-
マルチスレッド下のシングルトンパターン 2——ダブルロックを使用してオブジェクトのインスタンス化時のみ同期を確保 */ public class DoubleLockSingleton {
private static volatile DoubleLockSingleton instance;//静的インスタンス変数を定義
/**
- プライベートコンストラクタを定義し、外部からの new を防止 */ private DoubleLockSingleton(){ //初期化処理 }
/**
- グローバルアクセスポイントを提供
- @return クラスのインスタンス */ public static DoubleLockSingleton getInstance(){ if(instance == null) synchronized(DoubleLockSingleton.class){//同期ブロックに入る if(instance == null)//再度 null チェック instance = new DoubleLockSingleton(); } return instance; }
/*
- その他有用なプロパティと動作
- シングルトンパターンを適用したクラスでも本来の機能を保持する
- */ }
注意:ダブルロックは volatile キーワード(コンパイラに、この変数のコピーを保持しないよう指示。変更が発生すると強制的に書き戻され、不整合を防ぐ)と synchronized で修飾された同期ブロックに体现されます
しかし、これには代价がかかります。volatile キーワードはコンパイラに、このオブジェクトに対するコンパイル最適化を行わないよう指示します。最初の new オブジェクトプロセスのみを見ると、ダブルロックの効率は同期メソッドよりも低くなりますが、ダブルロック方式はその後の呼び出しでは同期が不要になるため、長期的に見ればダブルロックの効率は同期メソッドよりも高くなります
スローな同期機構を使用せずにスレッド��ーフを確保する方法はないでしょうか?もしあれば、効率を大幅に向上できるはずです。对吧?
もちろんあります。この方法は「早期初期化」(ついでに言うと、冒頭で言及した「クラシックなシングルトンパターン」は実際には「遅延初期化」の方法を使用しています。。とてもシンプルで、説明の必要はありません)と呼ばれます。一緒に見てみましょう:
package SingletonPattern;/**
-
@author ayqy
-
マルチスレッド環境下のシングルトンパターン——「早期初期化」を使用してスレッドセーフを確保 */ public class EagerlyInitSingleton {
private static EagerlyInitSingleton instance = new EagerlyInitSingleton();//静的インスタンス変数を定義し、クラスロード時に初期化処理を実行
/**
- プライベートコンストラクタを定義し、外部からの new を防止 */ private EagerlyInitSingleton(){ //初期化処理 }
/**
- グローバルアクセスポイントを提供
- @return クラスのインスタンス */ public static synchronized EagerlyInitSingleton getInstance(){ return instance; }
/*
- その他有用なプロパティと動作
- シングルトンパターンを適用したクラスでも本来の機能を保持する
- */ }
えっ、これも方法と呼べるのでしょうか?このやり方は標準に合っていないように思えます。。没关系、この方法にはもちろん利点があります。例えば:
1.効率が非常に高く、スレッドセーフ;2.シンプルで使いやすく、何も考慮する必要がなく、判断すら不要
しかし、致命的な欠点は:リソースの浪費問題です。もしこのオブジェクトが巨大で極めてリソースを消費するオブジェクトであり、最初に作成したのに迟迟く使用しなかった場合、これは非常に痛手です。。
上記で 3 種類のスレッド同期を確保する方法を言及しました。どのように選択するかは具体的な状況に応じて決定する必要があります。効率、リソース利用など、さまざまな要素を総合的に考慮する必要があります
五.複数の class loader 環境下のシングルトンパターン
複数のクラスローダーが存在する場合、複数のクラスローダーが同時にシングルトンクラスをロードし、複数のインスタンスを生成する可能性があります
この状況に対して、どの class loader を使用してシングルトンクラスをロードするかを明示的に指定することで、上記の問題を効果的に回避できます
六.まとめ
シングルトンパターンを適用することでオブジェクトの一意性を確保できます。しかし、シングルトンパターンの適用範囲に注意する必要があります。シングルトンパターンを乱用してはいけません。管理すべきリソースに敏感なオブジェクトはそれほど多くないからです
コメントはまだありません