English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
javaデザインパターンのシングルトン・パターン
前書き:
ソフトウェア開発の過程で、たとえばスレッドプール(threadpool)、キャッシュ(cache)、ダイアログ、設定など、必要なオブジェクトが一つだけ必要なことがよくあります。これらのオブジェクトが複数のインスタンスとして生成された場合、不必要なトラブル、例えばプログラムの挙動異常、リソースの過剰使用などが発生する可能性があります。この場合、シングルトン・パターンは、あるクラスがたった一つのインスタンスを持つことを保証し、グローバルなアクセスポイントを提供することができます。以下は、シンプルなシングルトン・クラスからどのようにシングルトン・パターンを実現するかを探るものです。
/** * 最も古典的な単例クラス */ public class Singleton { // 静的変数として設定して、Singletonのユニークなインスタンスを記録します。 private static Singleton singleInstance; private Singleton(){ // コンストラクタをプライベートに宣言することで、Singletonクラス内でのみこのコンストラクタを呼び出すことができます。 } /* * Singletonオブジェクトを取得し、まだインスタンス化されていない場合は、インスタンスを作成してそのインスタンスを返します。 */ public static Singleton getInstance(){ if (singleInstance == null) { singleInstance = new Singleton(); } return singleInstance; } // 他のメソッド }
上記の例では、Singletonクラスがこのクラスのインスタンス化プロセスを自分で管理し、静的のgetInstance()メソッドを設定することで、他のクラスがSingletonを使用する際にインスタンスを返すグローバルアクセスポイントを提供しています。この単例モードの利点は、インスタンス化を遅延することです。簡単に言えば、遅延インスタンス化とは、クラスが必要なときにそのインスタンスを作成するのではなく、このクラスをロードしたときにインスタンスを作成することを意味します。この利点は、性能の無駄を避けることができます。例えば、プログラムの最初から使用されないオブジェクトや、プログラムの実行中に使用されないオブジェクトがあります。しかし、この例には欠点もあります。それは、スレッドセーフでないことです。複数のスレッドが同時にgetInstance()メソッドに到達し、Singletonがまだnew Singleton()のインスタンスを持っていない場合、すべてのスレッドがsingleInstanceがnullであると認識し、Singletonをインスタンス化します。これにより、複数のSingletonインスタンスが生成され、単例モードの本意に反しています。したがって、次にその改善を行う必要があります。
public class SingletonA { private static SingletonA singletongA; private SingletonA(){ } /* * synchronizedキーワードを追加して、getSingletonAメソッドを同期メソッドにします。 */ public static synchronized SingletonA getInstanceA(){ if (singletongA == null) { singletongA = new SingletonA(); } return singletongA; } // 他のメソッド }
この例では、synchronizedを使用することでgetInstanceA()を同期メソッドに変え、スレッドがこのメソッドにアクセスする前に他のスレッドがこのメソッドから離れるのを待つ必要があります。これにより、このメソッドは同時に1つのスレッドのみが実行できます。
問題は解決したかもしれませんが、同期メソッドはプログラムの実行効率に影響を与えます。この例では、最初の例でgetInstance()メソッドが最初に実行される際に複数のインスタンスが生成されないようにするために使用しましたが、この例では、インスタンスが既に存在する場合、getInstanceA()同期メソッドを呼び出すたびに同期が必要で、これは余計な負担となります。なぜなら、単例クラスが新しいインスタンスを作成されることを心配する必要がないからです。したがって、さらに改善する必要があります。
遅延インスタンス化について述べたところで、使わない場合、それほど複雑ではありません。
public class SingletonB { // 静的初期化子(static initializer)で単例を作成し、スレッドセーフを保証します private static SingletonB singletonB = new SingletonB(); private SingletonB(){ // コンストラクタ } public static SingletonB getInstaceB(){ // 既にインスタンス化されていますので、直接使用してください return singletonB; } }
上記の方法は、JVMがこのクラスをロードする際にすぐにインスタンスを作成することです。なぜなら、JVMはスレッドがこのインスタンスにアクセスする前にそのインスタンスを作成するからです。したがって、スレッドは安全です。しかし、遅延インスタンス化に比べてリソースの無駄になる可能性があります。また、このクラスが大きい場合、プログラムの初期化時間が長くなる可能性があります。
それでは、遅延インスタンス化を使用しながらもスレッド不安全を引き起こさず、アクセス効率を向上させることはできますか。次に、ダブルチェックロックを使用して改善してみましょう。
/** * 重複ロック単例パターン */ public class SingletonC { private volatile static SingletonC singletonC; private SingletonC(){ } public static SingletonC getInstanceC(){ if (singletonC == null) { synchronized (SingletonC.class) { if (singletonC == null) { singletonC = new SingletonC(); } } } return singletonC; } }
上記の例では、まずインスタンスをチェックし、存在しない場合にシンクロナイズブロックに進みます。シンクロナイズブロックに入った後も再びチェックし、nullであればインスタンスを作成します。したがって、singletonC = new SingletonC()は一度だけ実行されます。その後のgetInstanceC()の呼び出しでは、すでにインスタンスがあるため、直接返されます。したがって、最初の呼び出し時のみシンクロナイズが実行され、その後は2番目の例のように毎回シンクロナイズメソッドを走行することはありません。これにより、getInstanceC()の実行時間を短縮できます。ここでvolatileキーワードが見つかりますが、singletonCが初期化された後、すべてのスレッドに対して可见であり、複数のスレッドがSingletonC変数を正しく処理できるようにします。ただし、volatileキーワードはJava 5その後の使用に際して、このバージョン以前のバージョンではこの重複チェックが無効になることがあります。
シングルトンモードを使用する際には、複数のクラスローダ(classloader)がある場合、自分でクラスローダを指定し、一つのクラスローダを使用する必要があります。なぜなら、各クラスローダは命名空間を定義し、異なるクラスローダが同じクラスをロードする可能性があるため、シングルトンクラスが複数のインスタンスを作成する原因となります。
ご覧いただきありがとうございます。皆様のサポートに感謝します。
声明:本稿の内容はインターネットから収集され、著作権者に帰属します。インターネットユーザーが自発的に貢献し、アップロードした内容であり、本サイトは所有権を持ちません。また、人工的な編集は行われていません。著作権侵害を疑う内容がある場合は、メールを送信して:notice#oldtoolbag.com(メールを送信する際には、#を@に置き換えてください。通報を行い、関連する証拠を提供してください。一旦確認がとりさえされると、本サイトは侵害を疑われるコンテンツをすぐに削除します。