Javaでシングルトン設計パターンを実装する効率的な方法は何ですか?
29 に答える
列挙型を使用します。
public enum Foo {
INSTANCE;
}
Joshua Bloch は、Google I/O 2008 での「 Effective Java Reloaded 」の講演で、このアプローチについて説明しました: link to video。彼のプレゼンテーション ( effective_java_reloaded.pdf )のスライド 30 ~ 32 も参照してください。
シリアライズ可能なシングルトンを実装する正しい方法
public enum Elvis { INSTANCE; private final String[] favoriteSongs = { "Hound Dog", "Heartbreak Hotel" }; public void printFavorites() { System.out.println(Arrays.toString(favoriteSongs)); } }
編集:「Effective Java」のオンライン部分には次のように書かれています:
「このアプローチは、パブリック フィールド アプローチと機能的に同等ですが、より簡潔であり、シリアライゼーション メカニズムを無料で提供し、複雑なシリアライゼーションまたはリフレクション アタックに直面した場合でも、複数のインスタンス化に対する鉄壁の保証を提供します。このアプローチは、まだ広く採用されていないため、単一要素の列挙型が singleton を実装する最良の方法です。」
使い方によって、いくつかの「正解」があります。
Java 5 以降、これを行う最善の方法は列挙型を使用することです。
public enum Foo {
INSTANCE;
}
Java 5 より前の最も単純なケースは次のとおりです。
public final class Foo {
private static final Foo INSTANCE = new Foo();
private Foo() {
if (INSTANCE != null) {
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
return INSTANCE;
}
public Object clone() throws CloneNotSupportedException{
throw new CloneNotSupportedException("Cannot clone instance of this class");
}
}
コードを見てみましょう。まず、クラスを final にする必要があります。この場合、final
キーワードを使用して、それが最終的なものであることをユーザーに知らせました。次に、コンストラクターをプライベートにして、ユーザーが独自の Foo を作成できないようにする必要があります。コンストラクターから例外をスローすると、ユーザーはリフレクションを使用して 2 番目の Foo を作成できなくなります。次にprivate static final Foo
、唯一のインスタンスを保持するフィールドと、public static Foo getInstance()
それを返すメソッドを作成します。Java 仕様では、クラスが最初に使用されたときにのみコンストラクターが呼び出されるようになっています。
非常に大きなオブジェクトまたは重い構造コードがあり、インスタンスが必要になる前に使用される可能性のある他のアクセス可能な静的メソッドまたはフィールドもある場合は、遅延初期化を使用する必要があります。
を使用しprivate static class
てインスタンスをロードできます。コードは次のようになります。
public final class Foo {
private static class FooLoader {
private static final Foo INSTANCE = new Foo();
}
private Foo() {
if (FooLoader.INSTANCE != null) {
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
return FooLoader.INSTANCE;
}
}
この行private static final Foo INSTANCE = new Foo();
はクラス FooLoader が実際に使用されたときにのみ実行されるため、遅延インスタンス化が処理され、スレッドセーフであることが保証されます。
オブジェクトをシリアル化できるようにしたい場合は、逆シリアル化によってコピーが作成されないようにする必要があります。
public final class Foo implements Serializable {
private static final long serialVersionUID = 1L;
private static class FooLoader {
private static final Foo INSTANCE = new Foo();
}
private Foo() {
if (FooLoader.INSTANCE != null) {
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
return FooLoader.INSTANCE;
}
@SuppressWarnings("unused")
private Foo readResolve() {
return FooLoader.INSTANCE;
}
}
メソッドreadResolve()
は、オブジェクトがプログラムの前回の実行でシリアル化された場合でも、唯一のインスタンスが返されるようにします。
免責事項:すべてのすばらしい回答を要約し、自分の言葉で書きました。
シングルトンを実装する際には、2 つのオプションがあります。
- 遅延読み込み
- アーリーローディング
遅延読み込みは少しオーバーヘッドを追加します (正直なところ、かなりの量です)。そのため、非常に大きなオブジェクトまたは重い構築コードがあり、インスタンスが必要になる前に使用される可能性のある他のアクセス可能な静的メソッドまたはフィールドがある場合にのみ使用してください。次に、遅延初期化を使用する必要があります。それ以外の場合は、早期読み込みを選択することをお勧めします。
シングルトンを実装する最も簡単な方法は次のとおりです。
public class Foo {
// It will be our sole hero
private static final Foo INSTANCE = new Foo();
private Foo() {
if (INSTANCE != null) {
// SHOUT
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
return INSTANCE;
}
}
早期にロードされたシングルトンであることを除いて、すべてが良好です。遅延ロードされたシングルトンを試してみましょう
class Foo {
// Our now_null_but_going_to_be sole hero
private static Foo INSTANCE = null;
private Foo() {
if (INSTANCE != null) {
// SHOUT
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
// Creating only when required.
if (INSTANCE == null) {
INSTANCE = new Foo();
}
return INSTANCE;
}
}
ここまでは順調ですが、私たちのヒーローの多くのインスタンスを必要とする複数の悪のスレッドと単独で戦っている間、私たちのヒーローは生き残れません。それでは、邪悪なマルチスレッドから保護しましょう。
class Foo {
private static Foo INSTANCE = null;
// TODO Add private shouting constructor
public static Foo getInstance() {
// No more tension of threads
synchronized (Foo.class) {
if (INSTANCE == null) {
INSTANCE = new Foo();
}
}
return INSTANCE;
}
}
しかし、ヒーローを守るだけでは十分ではありません。これは、ヒーローを助けるためにできる/すべき最善のことです。
class Foo {
// Pay attention to volatile
private static volatile Foo INSTANCE = null;
// TODO Add private shouting constructor
public static Foo getInstance() {
if (INSTANCE == null) { // Check 1
synchronized (Foo.class) {
if (INSTANCE == null) { // Check 2
INSTANCE = new Foo();
}
}
}
return INSTANCE;
}
}
これは、「ダブルチェック ロック イディオム」と呼ばれます。volatile ステートメントは忘れがちで、なぜそれが必要なのかを理解するのは困難です。詳細については、「ダブルチェック ロックが壊れている」宣言を参照してください。
これで悪質スレッドは確定ですが、残虐な連載はどうなるでしょうか?デシリアライズ中であっても、新しいオブジェクトが作成されないようにする必要があります。
class Foo implements Serializable {
private static final long serialVersionUID = 1L;
private static volatile Foo INSTANCE = null;
// The rest of the things are same as above
// No more fear of serialization
@SuppressWarnings("unused")
private Object readResolve() {
return INSTANCE;
}
}
メソッドreadResolve()
は、オブジェクトがプログラムの前回の実行でシリアル化された場合でも、唯一のインスタンスが返されるようにします。
最後に、スレッドとシリアライゼーションに対する十分な保護を追加しましたが、コードはかさばって見苦しく見えます。私たちのヒーローを変身させましょう:
public final class Foo implements Serializable {
private static final long serialVersionUID = 1L;
// Wrapped in a inner static class so that loaded only when required
private static class FooLoader {
// And no more fear of threads
private static final Foo INSTANCE = new Foo();
}
// TODO add private shouting construcor
public static Foo getInstance() {
return FooLoader.INSTANCE;
}
// Damn you serialization
@SuppressWarnings("unused")
private Foo readResolve() {
return FooLoader.INSTANCE;
}
}
はい、これは私たちとまったく同じヒーローです:)
この行はクラスが実際に使用private static final Foo INSTANCE = new Foo();
されたときにのみ実行されるため、これにより遅延インスタンス化が処理され、スレッドセーフであることが保証されます。FooLoader
そして、私たちはここまで来ました。私たちが行ったすべてを達成するための最良の方法は、可能な限り最善の方法です。
public enum Foo {
INSTANCE;
}
内部的には次のように扱われます
public class Foo {
// It will be our sole hero
private static final Foo INSTANCE = new Foo();
}
それでおしまい!シリアライゼーション、スレッド、醜いコードの心配はもうありません。また、ENUMS シングルトンは遅延初期化されます。
このアプローチは、パブリック フィールド アプローチと機能的に同等ですが、より簡潔であり、シリアライゼーション メカニズムを無料で提供し、洗練されたシリアライゼーションまたはリフレクション アタックに直面した場合でも、複数のインスタンス化に対する鉄壁の保証を提供します。このアプローチはまだ広く採用されていませんが、単一要素の列挙型がシングルトンを実装する最良の方法です。
-「Effective Java」の Joshua Bloch
これで、ENUMS がシングルトンを実装する最良の方法と見なされている理由に気付いたかもしれません。ご辛抱いただきありがとうございます :)
ブログで更新しました。
Stu Thompson によって投稿されたソリューションは、Java 5.0 以降で有効です。しかし、エラーが発生しやすいと思うので、使用しないことをお勧めします。
volatile ステートメントは忘れがちで、なぜそれが必要なのかを理解するのは困難です。volatile がなければ、二重チェックのロック アンチパターンにより、このコードはもはやスレッド セーフではありません。これについては、 Java Concurrency in Practiceの 16.2.4 段落を参照してください。要するに、このパターン (Java 5.0 より前、または volatile ステートメントがない場合) は、(まだ) 正しくない状態にある Bar オブジェクトへの参照を返す可能性があります。
このパターンは、パフォーマンスの最適化のために考案されました。しかし、これはもはや本当の懸念ではありません。次の遅延初期化コードは高速で、さらに重要なことに、読みやすいです。
class Bar {
private static class BarHolder {
public static Bar bar = new Bar();
}
public static Bar getBar() {
return BarHolder.bar;
}
}
Java 5 以降のスレッドセーフ:
class Foo {
private static volatile Bar bar = null;
public static Bar getBar() {
if (bar == null) {
synchronized(Foo.class) {
if (bar == null)
bar = new Bar();
}
}
return bar;
}
}
volatile
ここで修飾子に注意してください。:) これがないと、JMM (Java メモリ モデル) によって他のスレッドがその値の変更を確認することが保証されないため、重要です。同期はそれを考慮せず、そのコード ブロックへのアクセスをシリアル化するだけです。
@Bno の回答では、Bill Pugh (FindBugs) が推奨するアプローチが詳しく説明されており、議論の余地があります。彼の答えも読んで投票してください。
遅延初期化を忘れてください。問題ありすぎます。これが最も簡単な解決策です。
public class A {
private static final A INSTANCE = new A();
private A() {}
public static A getInstance() {
return INSTANCE;
}
}
本当に必要かどうかを確認してください。「シングルトンアンチパターン」をGoogle検索して、それに対するいくつかの議論を確認してください。
本質的に問題はないと思いますが、グローバル リソース/データを公開するためのメカニズムにすぎないため、これが最善の方法であることを確認してください。特に、単体テストも使用している場合は、依存性注入(DI) がより便利であることがわかりました。これは、DI を使用すると、モック化されたリソースをテスト目的で使用できるためです。
シングルトンを使用する代わりに依存性注入(DI)を提案するいくつかの答えに私は不思議に思っています。これらは無関係の概念です。DIを使用して、シングルトンまたは非シングルトン(スレッドごとなど)のインスタンスを注入できます。Spring 2.xを使用している場合、少なくともこれは当てはまります。他のDIフレームワークについて話すことはできません。
したがって、OPに対する私の答えは(最も些細なサンプルコードを除いて)次のようになります。
- Spring FrameworkのようなDIフレームワークを使用してから、
- 依存関係がシングルトン、リクエストスコープ、セッションスコープなど、DI構成の一部にします。
このアプローチは、シングルトンを使用するかどうかが簡単に元に戻せる実装の詳細である、優れた分離(したがって柔軟でテスト可能な)アーキテクチャを提供します(もちろん、使用するシングルトンがスレッドセーフである場合)。
シングルトンを作成する前に、なぜシングルトンが必要なのかをよく考えてください。Java でシングルトンをグーグル検索すると、非常に簡単につまずくことができるそれらの使用について、準宗教的な議論があります。
個人的には、多くの理由からできるだけ頻繁にシングルトンを避けるようにしています。シングルトンは誰もが理解しやすいため、悪用されることが多いと思います。これらは、「グローバル」データを OO 設計に取り込むためのメカニズムとして使用され、オブジェクトのライフサイクル管理を簡単に回避できる (または、B 内から A を実行する方法を実際に考えている) ために使用されます。制御の反転(IoC) や依存性注入(DI) などを検討して、適切な妥協点を見つけてください。
本当に必要な場合は、ウィキペディアにシングルトンの適切な実装の良い例があります。
以下は、3 つの異なるアプローチです。
列挙型
/** * Singleton pattern example using Java Enum */ public enum EasySingleton { INSTANCE; }
二重チェックのロック/遅延読み込み
/** * Singleton pattern example with Double checked Locking */ public class DoubleCheckedLockingSingleton { private static volatile DoubleCheckedLockingSingleton INSTANCE; private DoubleCheckedLockingSingleton() {} public static DoubleCheckedLockingSingleton getInstance() { if(INSTANCE == null) { synchronized(DoubleCheckedLockingSingleton.class) { // Double checking Singleton instance if(INSTANCE == null) { INSTANCE = new DoubleCheckedLockingSingleton(); } } } return INSTANCE; } }
静的ファクトリ メソッド
/** * Singleton pattern example with static factory method */ public class Singleton { // Initialized during class loading private static final Singleton INSTANCE = new Singleton(); // To prevent creating another instance of 'Singleton' private Singleton() {} public static Singleton getSingleton() { return INSTANCE; } }
Spring Frameworkを使用してシングルトンを管理しています。
クラスの「シングルトン性」を強制するわけではありません (複数のクラス ローダーが関係している場合は実際には実行できません)。オブジェクト。
シングルトンの実装には多くのニュアンスがあります。ホルダーパターンは多くの場合使用できません。また、揮発性を使用する場合はIMO-ローカル変数も使用する必要があります。最初から始めて、問題を繰り返しましょう。あなたは私が何を意味するかを見るでしょう。
最初の試行は次のようになります。
public class MySingleton {
private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new MySingleton();
}
return INSTANCE;
}
...
}
ここには、 INSTANCEというプライベートな静的メンバーと、getInstance() というパブリックな静的メソッドを持つ MySingleton クラスがあります。getInstance() が初めて呼び出されたとき、INSTANCEメンバーは null です。その後、フローは作成条件に入り、MySingleton クラスの新しいインスタンスを作成します。getInstance() への後続の呼び出しでは、INSTANCE変数が既に設定されていることが検出されるため、別の MySingleton インスタンスは作成されません。これにより、getInstance() のすべての呼び出し元で共有される MySingleton のインスタンスが 1 つだけ存在することが保証されます。
しかし、この実装には問題があります。マルチスレッド アプリケーションでは、単一インスタンスの作成時に競合状態が発生します。複数の実行スレッドが同時に (またはその前後に) getInstance() メソッドにヒットすると、それぞれのスレッドでINSTANCEメンバーが null として認識されます。これにより、各スレッドが新しい MySingleton インスタンスを作成し、続いてINSTANCEメンバーを設定します。
private static MySingleton INSTANCE;
public static synchronized MySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new MySingleton();
}
return INSTANCE;
}
ここでは、getInstance() メソッドを同期するために、メソッド シグネチャで synchronized キーワードを使用しています。これにより、競合状態が確実に修正されます。スレッドはブロックされ、一度に 1 つずつメソッドに入ります。しかし、これはパフォーマンスの問題も引き起こします。この実装は、単一インスタンスの作成を同期するだけではありません。読み取りを含む getInstance() へのすべての呼び出しを同期します。読み取りは単にINSTANCEの値を返すため、同期する必要はありません。読み取りは呼び出しの大部分を占めるため (インスタンス化は最初の呼び出しでのみ行われることを思い出してください)、メソッド全体を同期することで不要なパフォーマンス ヒットが発生します。
private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronize(MySingleton.class) {
INSTANCE = new MySingleton();
}
}
return INSTANCE;
}
ここでは、同期をメソッド シグネチャから MySingleton インスタンスの作成をラップする同期ブロックに移動しました。しかし、これで私たちの問題は解決しますか? 読み取りをブロックすることはもうありませんが、一歩後退しています。複数のスレッドが同時にまたはほぼ同時に getInstance() メソッドをヒットし、すべてのスレッドでINSTANCEメンバーが null として認識されます。
次に、ロックを取得してインスタンスを作成する同期ブロックにヒットします。そのスレッドがブロックを終了すると、他のスレッドがロックを求めて競合し、各スレッドが 1 つずつブロックを通過して、クラスの新しいインスタンスを作成します。それで、私たちは出発点に戻ってきました。
private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronized(MySingleton.class) {
if (INSTANCE == null) {
INSTANCE = createInstance();
}
}
}
return INSTANCE;
}
ここで、ブロック内から別のチェックを発行します。INSTANCEメンバーが既に設定されている場合は、初期化をスキップします。これは、ダブルチェック ロックと呼ばれます。
これにより、複数のインスタンス化の問題が解決されます。しかし、繰り返しになりますが、私たちのソリューションは別の課題を提示しました。他のスレッドは、 INSTANCEメンバーが更新されたことを「認識」しない場合があります。これは、Java がメモリ操作を最適化する方法によるものです。
スレッドは、変数の元の値をメイン メモリから CPU のキャッシュにコピーします。値への変更は、そのキャッシュに書き込まれ、キャッシュから読み取られます。これは、パフォーマンスを最適化するために設計された Java の機能です。しかし、これはシングルトンの実装に問題を引き起こします。別のキャッシュを使用して別の CPU またはコアによって処理されている 2 番目のスレッドは、最初のスレッドによって行われた変更を認識しません。これにより、2 番目のスレッドはINSTANCEメンバーを null として認識し、シングルトンの新しいインスタンスを強制的に作成します。
private static volatile MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronized(MySingleton.class) {
if (INSTANCE == null) {
INSTANCE = createInstance();
}
}
}
return INSTANCE;
}
これは、 INSTANCEメンバーの宣言でvolatileキーワードを使用して解決します。これにより、コンパイラは、CPU キャッシュではなく、常にメイン メモリから読み書きするように指示されます。
しかし、この単純な変更にはコストがかかります。CPU キャッシュをバイパスしているため、揮発性INSTANCEメンバーを 4 回操作するたびにパフォーマンスが低下します。存在を再確認し (1 と 2)、値を設定し (3)、値を返します (4)。メソッドの最初の呼び出し中にのみインスタンスを作成するため、このパスはフリンジ ケースであると主張できます。おそらく、作成時のパフォーマンス ヒットは許容範囲内です。しかし、主なユースケースである読み取りでさえ、volatile メンバーを 2 回操作します。一度存在を確認し、もう一度その値を返します。
private static volatile MySingleton INSTANCE;
public static MySingleton getInstance() {
MySingleton result = INSTANCE;
if (result == null) {
synchronized(MySingleton.class) {
result = INSTANCE;
if (result == null) {
INSTANCE = result = createInstance();
}
}
}
return result;
}
パフォーマンス ヒットは volatile メンバーを直接操作することが原因であるため、ローカル変数を volatile の値に設定し、代わりにローカル変数を操作しましょう。これにより、揮発性で操作する回数が減り、失われたパフォーマンスの一部が取り戻されます。同期ブロックに入るときは、ローカル変数を再度設定する必要があることに注意してください。これにより、ロックを待っている間に発生した変更を反映して最新の状態になります。
これについて最近記事を書きました。シングルトンの解体。これらの例の詳細と「ホルダー」パターンの例については、こちらを参照してください。double-checked volatile アプローチを示す実際の例もあります。
ウィキペディアには、Java でもシングルトンの例がいくつかあります。Java 5 の実装はかなり完成しているように見え、スレッドセーフです (二重チェックのロックが適用されています)。
バージョン 1:
public class MySingleton {
private static MySingleton instance = null;
private MySingleton() {}
public static synchronized MySingleton getInstance() {
if(instance == null) {
instance = new MySingleton();
}
return instance;
}
}
遅延読み込み、ブロッキングによるスレッド セーフ、synchronized
.
バージョン 2:
public class MySingleton {
private MySingleton() {}
private static class MySingletonHolder {
public final static MySingleton instance = new MySingleton();
}
public static MySingleton getInstance() {
return MySingletonHolder.instance;
}
}
遅延読み込み、ノンブロッキングでスレッドセーフ、高性能。
遅延読み込みが必要ない場合は、次のことを試してください。
public class Singleton {
private final static Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() { return Singleton.INSTANCE; }
protected Object clone() {
throw new CloneNotSupportedException();
}
}
遅延読み込みが必要で、シングルトンをスレッドセーフにしたい場合は、ダブルチェック パターンを試してください。
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if(null == instance) {
synchronized(Singleton.class) {
if(null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
protected Object clone() {
throw new CloneNotSupportedException();
}
}
二重チェック パターンが機能することが保証されていないため (コンパイラの問題があるため、これ以上のことはわかりません)、getInstance メソッド全体を同期するか、すべてのシングルトンのレジストリを作成することもできます。
列挙型シングルトンと言えます。
Java で列挙型を使用するシングルトンは、通常、列挙型シングルトンを宣言する方法です。列挙型シングルトンには、インスタンス変数とインスタンス メソッドが含まれる場合があります。簡単にするために、インスタンスメソッドを使用している場合、オブジェクトの状態に影響を与える場合は、そのメソッドのスレッドセーフを確保する必要があることにも注意してください。
列挙型の使用は実装が非常に簡単であり、他の方法で回避する必要があるシリアライズ可能なオブジェクトに関して欠点はありません。
/**
* Singleton pattern example using a Java Enum
*/
public enum Singleton {
INSTANCE;
public void execute (String arg) {
// Perform operation here
}
}
でアクセスでき、SingletonSingleton.INSTANCE
でメソッドを呼び出すよりもはるかに簡単です。getInstance()
1.12 列挙型定数のシリアル化
列挙型定数は、通常のシリアライズ可能オブジェクトまたは外部化可能オブジェクトとは異なる方法でシリアライズされます。enum 定数のシリアル化された形式は、その名前だけで構成されます。定数のフィールド値がフォームに存在しません。列挙型定数をシリアル化するに
ObjectOutputStream
は、列挙型定数の name メソッドによって返される値を書き込みます。enum 定数を逆シリアル化するにObjectInputStream
は、ストリームから定数名を読み取ります。デシリアライズされた定数は、メソッドを呼び出すことによって取得されjava.lang.Enum.valueOf
、引数として受け取った定数名とともに定数の列挙型を渡します。他のシリアライズ可能または外部化可能オブジェクトと同様に、列挙型定数は、シリアライゼーション ストリームに続いて現れる後方参照のターゲットとして機能できます。列挙型定数をシリアル化するプロセスはカスタマイズできません。列挙型によって定義されたクラス固有
writeObject
の 、readObject
、readObjectNoData
、writeReplace
およびreadResolve
メソッドは、シリアル化および逆シリアル化中に無視されます。同様に、serialPersistentFields
またはserialVersionUID
フィールドの宣言も無視されます。すべての列挙型には固定serialVersionUID
の があり0L
ます。送信されるデータのタイプにはバリエーションがないため、列挙型のシリアル化可能なフィールドとデータを文書化する必要はありません。
従来のシングルトンのもう 1 つの問題は、インターフェイスを実装すると、Java のコンストラクターのように、メソッドが常に新しいインスタンスを返すSerializable
ため、シングルトンのままでなくなることです。これは、以下のようにシングルトンに置き換えて、新しく作成されたインスタンスreadObject()
を使用および破棄することで回避できます。readResolve()
// readResolve to prevent another instance of Singleton
private Object readResolve(){
return INSTANCE;
}
シングルトン クラスが状態を維持する場合、状態を一時的にする必要があるため、これはさらに複雑になる可能性がありますが、enum シングルトンでは、シリアル化が JVM によって保証されます。
よく読んだ
クラスのインスタンス変数を遅延してロードする必要がある場合は、二重チェックのイディオムが必要です。静的変数またはシングルトンを遅延してロードする必要がある場合は、初期化オンデマンド ホルダーイディオムが必要です。
さらに、シングルトンをシリアライズ可能にする必要がある場合は、他のすべてのフィールドを一時的にする必要があり、シングルトン オブジェクトを不変に維持するために readResolve() メソッドを実装する必要があります。そうしないと、オブジェクトが逆シリアル化されるたびに、オブジェクトの新しいインスタンスが作成されます。readResolve() が行うことは、readObject() によって読み取られた新しいオブジェクトを置き換えることです。これにより、その新しいオブジェクトを参照する変数がないため、ガベージ コレクションが強制されます。
public static final INSTANCE == ....
private Object readResolve() {
return INSTANCE; // Original singleton instance.
}
列挙シングルトン
スレッドセーフなシングルトンを実装する最も簡単な方法は、Enum を使用することです。
public enum SingletonEnum {
INSTANCE;
public void doSomething(){
System.out.println("This is a singleton");
}
}
このコードは、Java 1.5 で Enum が導入されてから機能します。
ダブルチェックロック
マルチスレッド環境 (Java 1.5 以降) で動作する「古典的な」シングルトンをコーディングする場合は、これを使用する必要があります。
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class){
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
volatile キーワードの実装が異なるため、これは 1.5 より前ではスレッドセーフではありません。
シングルトンの初期ロード (Java 1.5 より前でも動作)
この実装は、クラスのロード時にシングルトンをインスタンス化し、スレッド セーフを提供します。
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
public void doSomething(){
System.out.println("This is a singleton");
}
}
JSE 5.0 以降では、Enum アプローチを使用します。それ以外の場合は、静的シングルトン ホルダー アプローチ ((Bill Pugh によって説明された遅延読み込みアプローチ) を使用します。後者のソリューションは、特別な言語構造 (つまり、揮発性または同期化) を必要とせずにスレッドセーフでもあります)。
Java 1.5 の後でも、enum は、マルチスレッド環境でもインスタンスが 1 つしか作成されないことを保証するため、利用可能なシングルトン実装としては最適だと思います。
public enum Singleton {
INSTANCE;
}
そして、あなたは完了です!
この投稿を見てください。
Java のコア ライブラリの GoF デザイン パターンの例
ベストアンサーの「シングルトン」欄より、
シングルトン (毎回同じインスタンス (通常はそれ自体) を返す作成メソッドによって認識可能)
- java.lang.Runtime#getRuntime()
- java.awt.Desktop#getDesktop()
- java.lang.System#getSecurityManager()
Java ネイティブ クラス自体から Singleton の例を学ぶこともできます。
単純な " static Foo foo = new Foo();
" では不十分な場合があります。実行したい基本的なデータ挿入について考えてみてください。
一方、シングルトン変数をインスタンス化するメソッドはすべて同期する必要があります。同期自体は悪くありませんが、パフォーマンスの問題やロックにつながる可能性があります (この例を使用した非常にまれな状況で。解決策は次のとおりです)。
public class Singleton {
private static Singleton instance = null;
static {
instance = new Singleton();
// do some of your instantiation stuff here
}
private Singleton() {
if(instance!=null) {
throw new ErrorYouWant("Singleton double-instantiation, should never happen!");
}
}
public static getSingleton() {
return instance;
}
}
さてどうなる?クラスは、クラス ローダーを介してロードされます。クラスがバイト配列から解釈された直後に、VM はstatic { }ブロックを実行します。それがすべての秘密です。静的ブロックは、指定されたパッケージの指定されたクラス (名前) がこの 1 つのクラス ローダーによってロードされるときに 1 回だけ呼び出されます。