JavaRush /Java Blog /Random-JA /デザインパターン: シングルトン

デザインパターン: シングルトン

Random-JA グループに公開済み
こんにちは!今日はさまざまなデザイン パターンを詳しく見ていきます。まず、「シングルトン」とも呼ばれるシングルトン パターンから始めます。 デザインパターン: シングルトン - 1覚えておいてください: デザイン パターン一般について、私たちは何を知っているでしょうか? 設計パターンは、多くの既知の問題を解決するために従うことができるベスト プラクティスです。デザイン パターンは通常、どのプログラミング言語にも関連付けられていません。これらを一連の推奨事項として捉え、これに従うことで間違いを回避し、車輪の再発明をする必要がなくなります。

シングルトンとは何ですか?

シングルトンは、クラスに適用できる最も単純な設計パターンの 1 つです。「このクラスはシングルトンです」と言われることがありますが、これは、このクラスがシングルトン設計パターンを実装していることを意味します。場合によっては、オブジェクトを 1 つだけ作成できるクラスを作成することが必要になることがあります。たとえば、データベースへのログ記録や接続を担当するクラスです。シングルトン設計パターンは、そのようなタスクをどのように達成できるかを説明します。シングルトンは、次の 2 つのことを行うデザイン パターンです。
  1. クラスがそのクラスのインスタンスを 1 つだけ持つことを保証します。

  2. このクラスのインスタンスにグローバル アクセス ポイントを提供します。

したがって、シングルトン パターンのほぼすべての実装に特徴的な 2 つの機能があります。
  1. プライベートコンストラクター。クラス自体の外部にクラス オブジェクトを作成する機能を制限します。

  2. クラスのインスタンスを返すパブリック静的メソッド。このメソッドは と呼ばれますgetInstance。これは、クラス インスタンスへのグローバル アクセス ポイントです。

実装オプション

シングルトン デザイン パターンはさまざまな方法で使用されます。それぞれのオプションには、それぞれの意味で良い面と悪い面があります。いつものように、理想はありませんが、理想に向かって努力する必要があります。しかし、まず最初に、何が良くて何が悪いのか、またデザイン パターンの実装の評価にどのような指標が影響するのかを定義しましょう。ポジティブなことから始めましょう。実装に魅力と魅力を与える基準は次のとおりです。
  • 遅延初期化: アプリケーションの実行中に必要なときにクラスがロードされるとき。

  • コードの単純さと透明性: 指標はもちろん主観的なものですが、重要です。

  • スレッド セーフ: マルチスレッド環境で正しく動作します。

  • マルチスレッド環境での高いパフォーマンス: リソースを共有する際、スレッドは相互に最小限にブロックするか、まったくブロックしません。

次に短所です。実装を悪い観点から示す基準をリストしてみましょう。
  • 非遅延初期化: 必要かどうかに関係なく、アプリケーションの起動時にクラスがロードされるとき (矛盾していますが、IT の世界では遅延しているほうが良いのです)

  • コードが複雑で可読性が低い。指標も主観的なものです。目から血が出ている場合、実装はまあまあであると仮定します。

  • スレッドの安全性の欠如。つまり「スレッドハザード」です。マルチスレッド環境での誤った操作。

  • マルチスレッド環境でのパフォーマンスの低下: リソースを共有するときにスレッドが常に、または頻繁に相互にブロックします。

コード

これで、長所と短所を列挙して、さまざまな実装オプションを検討する準備が整いました。

シンプルな解決策


public class Singleton {
    private static final Singleton INSTANCE = new Singleton();
    
    private Singleton() {
    }
    
    public static Singleton getInstance() {
        return INSTANCE;
    }
}
最も単純な実装。長所:
  • コードのシンプルさと透明性

  • スレッドの安全性

  • マルチスレッド環境での高いパフォーマンス

マイナス点:
  • 遅延初期化ではありません。
最後の欠陥を修正するために、実装番号 2 を取得します。

遅延初期化


public class Singleton {
  private static Singleton INSTANCE;

  private Singleton() {}

  public static Singleton getInstance() {
    if (INSTANCE == null) {
      INSTANCE = new Singleton();
    }
    return INSTANCE;
  }
}
長所:
  • 遅延初期化。

マイナス点:
  • スレッドセーフではありません

実装は興味深いですね。遅延初期化は可能ですが、スレッドの安全性が失われます。問題ありません。実装 3 では、すべてを同期します。

同期アクセサー


public class Singleton {
  private static Singleton INSTANCE;

  private Singleton() {
  }

  public static synchronized Singleton getInstance() {
    if (INSTANCE == null) {
      INSTANCE = new Singleton();
    }
    return INSTANCE;
  }
}
長所:
  • 遅延初期化。

  • スレッドの安全性

マイナス点:
  • マルチスレッド環境でのパフォーマンスの低下

素晴らしい!実装その 3 では、スレッドの安全性を取り戻しました。確かに、遅いです...これでメソッドはgetInstance同期され、一度に 1 つずつしか入力できなくなります。実際、メソッド全体を同期する必要はなく、新しいクラス オブジェクトを初期化する部分だけを同期する必要があります。synchronizedしかし、新しいオブジェクトの作成を担当する部分を単純にブロックでラップすることはできません。これでは、スレッドの安全性が確保されません。もう少し複雑です。正しい同期方法は次のとおりです。

二重チェックされたロック


public class Singleton {
    private static Singleton INSTANCE;

  private Singleton() {
  }

    public static Singleton getInstance() {
        if (INSTANCE == null) {
            synchronized (Singleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
}
長所:
  • 遅延初期化。

  • スレッドの安全性

  • マルチスレッド環境での高いパフォーマンス

マイナス点:
  • 1.5 より前の Java バージョンではサポートされていません (volatile キーワードはバージョン 1.5 で修正されました)

この実装オプションが正しく機能するには、2 つの条件のうちの 1 つが必要であることに注意してください。変数は、 、 のいずれINSTANCEかである必要があります。今日説明する最後の実装は です。 finalvolatileClass Holder Singleton

クラスホルダーシングルトン


public class Singleton {

   private Singleton() {
   }

   private static class SingletonHolder {
       public static final Singleton HOLDER_INSTANCE = new Singleton();
   }

   public static Singleton getInstance() {
       return SingletonHolder.HOLDER_INSTANCE;
   }
}
長所:
  • 遅延初期化。

  • スレッドの安全性。

  • マルチスレッド環境での高いパフォーマンス。

マイナス点:
  • 正しく動作するには、クラス オブジェクトがSingletonエラーなく初期化されることを保証する必要があります。そうしないと、最初のメソッド呼び出しはgetInstanceエラーで終了しExceptionInInitializerError、後続のメソッド呼び出しはすべて失敗しますNoClassDefFoundError

実装はほぼ完璧です。そして遅延があり、スレッドセーフで高速です。しかし、マイナスにはニュアンスが記載されています。 シングルトン パターンのさまざまな実装の比較表:
実装 遅延初期化 スレッドの安全性 マルチスレッド速度 いつ使用しますか?
シンプルな解決策 - + 速い 一度もない。または、遅延初期化が重要ではない場合。しかし、決して良くありません。
遅延初期化 + - 適用できない マルチスレッドが必要ないときは常に
同期アクセサー + + ゆっくり 一度もない。または、マルチスレッドによる作業の速度が重要ではない場合。しかし、決して良くなるわけではありません
二重チェックされたロック + + 速い まれに、シングルトンの作成時に例外を処理する必要がある場合があります。(クラスホルダーシングルトン非適用時)
クラスホルダーシングルトン + + 速い マルチスレッドが必要な場合は常に、シングルトン クラス オブジェクトが問題なく作成されることが保証されます。

シングルトン パターンの長所と短所

一般に、シングルトンは期待どおりの動作をします。
  1. クラスがそのクラスのインスタンスを 1 つだけ持つことを保証します。

  2. このクラスのインスタンスにグローバル アクセス ポイントを提供します。

ただし、このパターンには次のような欠点があります。
  1. Singleton は SRP (単一責任原則) に違反しています。Singleton クラスは、その直接の責任に加えて、そのコピーの数も制御します。

  2. シングルトンに対する通常のクラスまたはメソッドの依存関係は、クラスのパブリック コントラクトには表示されません。

  3. グローバル変数はダメです。シングルトンは最終的に 1 つの大きなグローバル変数に変わります。

  4. シングルトンが存在すると、アプリケーション全般、特にシングルトンを使用するクラスのテスト容易性が低下します。

OK、もう終わりです。シングルトン設計パターンを調べました。これで、プログラマーの友人との一生の会話で、その製品の良い点だけでなく、悪い点についても一言で言えるようになります。新しい知識を習得できるよう頑張ってください。

追加の資料:

コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION