JAVAでのシングルトン実装

この記事では、オブジェクト指向プログラミングの最も一般的なパターンの1つであるシングルトンのテーマに触れたいと思います。 ただし、この場合、このパターンの長所/短所および適用分野については説明しませんが、JAVAでの実装に関する私の見解を表明するようにします。

一般的な情報
シングルトンパターンは、クラスが1つのインスタンスのみを持ち、グローバルアクセスポイントを提供することを保証します。


応用分野
1.)特定のクラスのインスタンスがシステムに複数存在してはなりません。
2.)インスタンスは、このクラスのすべてのクライアントから簡単にアクセスできる必要があります。
3.)オンデマンドオブジェクトの作成。つまり、システムの初期化中ではなく、初めて必要になったとき。

実装(JAVA):
現時点では、いくつかの実装オプションがあり、それぞれに欠点と利点があります。 私たちは今それらを理解しようとします。

オプション1は、問題を理解した直後に思い浮かぶ最も単純なものです。
オプション1

このソリューションには唯一の欠点があります。マルチスレッド環境では機能しないため、ほとんどの場合に適していません。 このソリューションは、シングルスレッドアプリケーションにのみ適しています。

次の解決策を提案し、提案します。

オプション2:
2番目のオプション

そして、マルチスレッドの問題は解決しましたが、次の2つの重要な点を失いました。
1.遅延初期化(インスタンスオブジェクトはクラス初期化中にクラスローダーによって作成されます)
2.コンストラクター呼び出し中に例外を処理する方法はありません。

このソリューションは、設計者に例外的な状況の危険がなく、遅延初期化の必要がない限り、マルチスレッドアプリケーションに適しています。

次に、2つのソリューションが発生します。
1.)内部クラスを使用する( Bill Pughの 「Initialization on Demand Holder」ソリューション)。
2.)同期を使用します。

ビル・ピューから始めましょう。

オプション3:
「オンデマンド初期化ホルダー」
3番目のオプション

この場合、遅延初期化の問題を完全に解決しました。オブジェクトはgetInstance()メソッドが最初に呼び出されたときに初期化されます。 しかし、コンストラクターでの例外処理にはまだ問題があります。 したがって、クラスコンストラクターが例外的な状況を作成する恐れを引き起こさない場合は、このメソッドを安全に使用できます。

同期する
この部分に特に注意を払いたいと思います。 「同期-神話と現実」という見出しでこの問題に取り組むことができます。

そして、最も簡単な方法です。

オプション4:
4番目のオプション
このオプションには1つの欠点しかありません。 同期は一度だけ有効です。getInstance()に初めてアクセスした後は、このメソッドにアクセスするたびに同期に時間がかかります。 これについて何が言えますか? まあ、まず、getInstance()の呼び出しが頻繁に行われない場合(つまり、「十分である」ということはあなた次第です)、このメソッドは他のメソッドよりも有利です-シンプルで理解しやすく、遅延初期化され、コンストラクターで例外的な状況を処理できるようにします。 そして第二に、Javaでの同期は、懸念されるほど負担が大きくなるのをやめました。 さて、幸福には他に何が必要ですか?

ここで、以前のバージョンで発生した問題を解決しようとする同期ソリューションを検討してください。

最も一般的な方法は、 Double-Checked Lockingです。 元のバージョンでは:
ダブルチェックロック

動作しません! なんで? これは別のトピックですが、興味のある方は、この記事http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.htmlを読むことをお勧めします。

ただし、JAVA 5ではvolatile修飾子を使用して問題を解決しました。 現時点では、ソリューションは次のようになっています。

オプション5:
5番目のオプション

このオプションは完璧なソリューションのように見えますが、使用することはお勧めしません。 同志のAllen Holubは、 volatile修飾子を使用すると、マルチプロセッサシステムでパフォーマンスの問題が発生する可能性があると指摘しました。 しかし、決めるのはあなた次第です。

ここでは、一般に、このパターンを実装するためのすべての一般的なオプション。 しかし、シングルトンの落とし穴はこれで終わりではありません。 シングルトンを使用して特定のアプリケーションを設計する際に考慮すべき点がいくつかあります。

落とし穴
1. 継承
シングルトンクラスのほとんどの場合、継承は不要であり、さらに、不必要に過剰設計の結果です。 はい。インスタンス自体とgetInstance()メソッドの両方が静的であるため、継承の実装には特定の困難が伴います。
したがって、反対の特別な必要がない限り、final修飾子を使用してこのクラスの継承を禁止することをお勧めします。

2. 2つ以上の仮想マシン
各仮想マシンは、Singletonオブジェクトの独自のコピーを作成します。 一見したところ明らかですが、EJB、JINI、RMIなどの多くの分散システムでは、事態はそれほど単純ではありません。 中間レベルが分散テクノロジーを隠す(透明にする)場合、オブジェクトがどこでいつ初期化されるかを言うのは困難です。
3. さまざまなクラスローダー
2つのクラスローダーがクラスをロードすると、それぞれがシングルトンの独自のコピーを作成できます(インスタンスがクラスローダーで初期化される場合)。 一部のアプリケーションサーバーの実装では、各サーブレットが独自のクラスローダーを持っているため、これは特にサーブレットを使用する場合に当てはまります。

関連性の低い問題(リフレクションテクノロジーやCloneableおよびSerializableインターフェースの実装など)がいくつかありますが、Singletonクラスのアプリケーションにおけるエキゾチックな性質のため、私には考慮されません。 しかし、いずれにせよ、私はこの資料に対する質問に喜んで答えます。

ここで、一般的に、そして私がこの記事でカバーしたかったすべてのキーポイント。 この資料が究極の真実であると主張せず、著者の視点とは異なる視点の存在を許可することに注意することだけが残っています。 それ以上に、どんなコメントでも歓迎します。

ご清聴ありがとうございました。

Source: https://habr.com/ru/post/J27108/


All Articles