English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Androidプログラミングデザインパターンのシングルトンモードの例詳細

この記事では、Androidプログラミングデザインパターンのシングルトンパターンについて説明します。皆さんに共有し、以下のように詳細を述べます:

一、紹介

シングルトンパターンは最も広く使用されるパターンの1つであり、多くの初心者エンジニアが使用する唯一のデザインパターンかもしれません。このパターンを適用する際には、シングルトンオブジェクトのクラスは常に一つのインスタンスを持つことを保証する必要があります。多くの場合、システム全体が一つのグローバルオブジェクトを持つ必要がありますが、これによりシステム全体の行動を調整するのに有利です。

二、定義

あるクラスが一つのインスタンスを持つことを確保し、そのインスタンスを独自にインスタンス化し、そのインスタンスをシステム全体に提供する。

三、使用シーン

あるクラスが一つのオブジェクトを持つことを確保するシーン、または多くのオブジェクトが生成されすぎるリソースを節約する場合、またはある種のオブジェクトが一つしか存在すべきでない場合。例えば、IOやデータベースなどのリソースにアクセスするためにオブジェクトを作成する際に多くのリソースが消費される場合、この場合、シングルトンパターンを使用を検討する必要があります。

四、実現方法

1、早いハン模式

サンプルコード:

/**
 * 早いハン模式
 */
public class Singleton {
  private static Singleton instance;
  private Singleton(){}
  public static Singleton getInstance(){
    if(instance == null){
      instance = new Singleton();
    }
    return instance;
  }
}

長所:遅延ロード(必要なときにのみ読み込む)

欠点:スレッドセーフでない、マルチスレッドで簡単にアシンクロナスな状況が発生する可能性がある、例えばデータベースオブジェクトに対する頻繁な読み書き操作時。

2、遅いハン模式

サンプルコード:

/**
 * 遅いハン模式
 */
public class Singleton {
  private static Singleton instance;
  private Singleton(){}
  public static synchronized Singleton getInstance(){
    if(instance == null){
      instance = new Singleton();
    }
    return instance;
  }
}

ハンガーメーカーモードと比較して、getInstance()メソッドにsynchronizedキーワードが追加されています。つまり、getInstanceはシンクロナイズメソッドです。これにより、多线程の状況でシングルトンオブジェクトのユニーク性を保証する手段となります。しかし、少し考えてみると、問題があることに気づくかもしれません。instanceが既に初期化されている場合(最初の呼び出し時にinstanceが初期化される)にもかかわらず、getInstanceメソッドの呼び出し毎にシンクロナイズが行われるため、不必要なリソース消費が発生します。これはラズパン(Lazy Initialization)パターンの最大の問題です。

長所:スレッド不安全な問題を解決しました。

欠点:最初のロード時には即时にインスタンス化を行う必要があります。反応が遅いため、最大の問題はgetInstanceの呼び出し毎にシンクロナイズが行われることで、不必要なシンクロナイズコストが発生することです。

補足:Androidのソースコードで使用されるこのシングルトンモードの方法には、InputMethodManager、AccessibilityManagerなどがこのシングルトンパターンを使用しています。

3、ダブルチェックロック(DCL)

サンプルコード:

/**
 * ダブルチェックロック(DCL)シングルトンパターン
 */
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;
  }
}

このプログラムの特徴は自然にgetInstanceメソッドにあります。getInstanceメソッドではinstanceに対して二重の空判定が行われています。第一層の判定は不必要なシンクロナイズを避けるために主な目的ですが、第二層の判定はnullの場合にインスタンスを作成するために行われています。

仮にスレッドAがinstance = new Singleton()ステートメントに達した場合、これは一見了一つの命令のように見えますが、実際にはそれは原子操作ではありません。このコードは最終的に複数のアセンブリ命令に変換され、以下のような処理を行います3することです:

(1)個のSingletonインスタンスにメモリを割り当てます;

(2)Singleton()のコンストラクタを呼び出し、メンバーフィールドを初期化します;

(3)はinstanceオブジェクトを割り当てられたメモリ空間に指す(この時点でinstanceはnullではありません)。

しかし、Javaコンパイラがプロセッサの乱序実行を許可しているため、およびJDK1.5JMM(Java Memory Model、つまりJavaメモリモデル)において、キャッシュ、レジスタからメインメモリへの書き戻し順序の規定によって、上記の第二、第三の文の順序が保証されないことがあります。つまり、実行順序は以下の通りかもしれません1-2-3もしくは1-3-2。もし後者であれば、それと同時に3完了、2実行される前に、スレッドBに切り替わるとき、この時点でinstanceはスレッドAで第三点を実行した後で、instanceは既に非空であるため、スレッドBは直接instanceを取り、使用するときにエラーが発生します。これがDCL無効化問題であり、この追跡が難しく再現が難しいエラーは長期間隠されがちです。

JDK1.5以降、SUN公式はこの問題に気付き、JVMを調整し、volatileキーワードを具体的化しました。したがって、JDKが1.5以降のバージョンでは、instanceの定義をprivate volatile static Singleton instanceに変更することで、instanceオブジェクトが常にメインメモリから読み取られることを保証し、DCLの書式で単例モードを完了することができます。もちろん、volatileは多少性能に影響を与えますが、プログラムの正確性を考慮すると、この程度の性能の犠牲は価値のあるものです。

長所:リソースの利用効率が高く、getInstanceが初めて実行される際に単例オブジェクトがインスタンス化されるため、効率が高くなります。並行が少ない、セキュリティが低い状況では、単例モードが非常に完璧に動作するかもしれません。

欠点:初回ロード時には反応が少し遅くなり、Javaメモリモデルの理由から時々失敗することがあります。高負荷環境ではある程度の欠点もありますが、発生確率は非常に低いです。

補足:androidのオープンソースプロジェクトAndroid-Universal-Image-Loader (https://github.com/nostra13/Android-Universal-Image-Loader)ではこの方法を使用しています。
DCLモードは最もよく使用される単例実現方法であり、必要なときにのみ単例オブジェクトをインスタンス化し、ほとんどのシーンで単例オブジェクトのユニーク性を保証できます。ただし、あなたのコードが並行シーンが非常に複雑であるか、JDK以下である場合を除いて。6バージョンを使用、そうでない場合は、この方法は一般的に必要を満たすことができます。

4、静的内部クラス単例モード

DCLはある程度の範囲でリソース消費、余分なシンクロナイズ、スレッドセーフな問題を解決しましたが、それでも、特定の状況で無効化される問題が発生します。この問題は「重複チェックロック(DCL)無効化」と呼ばれ、『Java並行プログラミング実践』の最後にこの問題が触れられ、この「最適化」は醜いものであり、推奨されません。代わりに以下のようのコードを使用することを提案します:

サンプルコード:

/**
 * 静的内部クラス単例モード
 */
public class Singleton {
  private Singleton(){}
  public static Singleton getInstance(){
    return SingletonHolder.instance;
  }
  /**
   * 静的内部クラス
   * 遅延ロード、メモリ消費を減らす
   */
  private static class SingletonHolder{}}
    private static final Singleton instance = new Singleton();
  }
}

Singletonクラスが最初に読み込まれたときは、instanceが初期化されません。SingletonのgetInstanceメソッドが最初に呼び出されたときにのみinstanceが初期化されます。したがって、getInstanceメソッドが最初に呼び出されたときは、仮想機がSingletonHolderクラスを読み込むことになります。この方法は、スレッドセーフを確保するだけでなく、単例オブジェクトのユニーク性を確保し、単例のインスタンス化を遅延させるため、推奨される単例モードの実装方法です。

長所:遅延ロード、スレッドセーフ(Javaでのクラスロード時の排他)、メモリ消費を減らします

5、エnum単例

前述の単例モードの実装方法について説明しましたが、これらの実装方法は少し複雑で、ある場合には問題が発生する可能性があります。

サンプルコード:

/**
 * エnum単例モード
 */
public enum Singleton {
  /**
   * 1.Javaから1.5始めます;
   * 2.シリアライズメカニズムを無料で提供します;
   * 3.絶対に複数のインスタンス化を防ぎます。複雑なシリアライズや反射攻撃に直面しても、そのようにします;
   */
  instance;
  private String others;
  Singleton() {
  }
  public String getOthers() {
    return others;
  }
  public void setOthers(String others) {
    this.others = others;
  }
}

シリアライズの簡潔さは、エnum单例の最大の利点です。エnumはJavaでは通常のクラスと同じであり、フィールドだけでなく、独自のメソッドを持つことができます。最も重要なのは、デフォルトのエnumインスタンスの生成はスレッドセーフであり、どんな状況でも常に単例です。

なぜそう言うのか?上記のいくつかの単例モードの実装では、ある状況でオブジェクトが再生成されることがあります。それはデシリアライズです。

シリアライズすることで、単例のインスタンスオブジェクトをディスクに書き込むことができ、読み戻してから効果的にインスタンスを取得することができます。コンストラクタがプライベートの場合でも、デシリアライズ時に特別な経路を通じてクラスの新しいインスタンスを作成することができ、そのクラスのコンストラクタを呼び出すのと同じです。デシリアライズ操作は特別なフック関数を提供しており、クラスにはプライベートでインスタンス化されたメソッドreadResolve()があり、開発者がオブジェクトのデシリアライズを制御できます。例えば、上記のいくつかの例で、デシリアライズ時に単例オブジェクトが新しいオブジェクトとして再生成されないようにするには、以下のようなメソッドを追加する必要があります:

private Object readResolve() throws ObjectStreamException {}}
  return instance;
}

:readResolveメソッドでinstanceオブジェクトを返すことを意図しており、デフォルトの新しいオブジェクトの再生成ではなくです。また、エnum(列挙型)の場合、この問題は存在しません。なぜなら、反序列化しても新しいインスタンスは再生成されません。

長所:シリアライズメカニズムを無料で提供し、複雑なシリアライズや反射攻撃に直面しても複数のインスタンス化を絶対に防ぎます。

欠点:Javaから1.5サポートを開始しました。

ここでは、シングルトンモードについて説明しました。5様々な作成方法があり、それぞれの長所と短所を考慮して、個々の実際のプロジェクトで使用することができます。

Androidに関するさらに詳しい内容に興味を持たれる読者は、当サイトの特集を確認してください:《Android開発入門と進階チュートリアル》、《Androidデバッグ技術とよくある問題の解決方法》、《Android基本コンポーネントの使い方のまとめ》、《AndroidビューViewの技術的まとめ》、《Androidレイアウトlayoutの技術的まとめ》および《Androidコントロールの使い方のまとめ》

この記事が皆様のAndroidプログラム設計に役立つことを願っています。

声明:本文の内容はインターネットから取得しており、著作権者に帰属します。インターネットユーザーにより自発的に提供された内容であり、当サイトは所有権を持ちません。また、人工編集は行われていません。著作権侵害が疑われる場合は、以下のメールアドレスまでご連絡ください:notice#oldtoolbag.com(メールの際は、#を@に変更してください)で通報し、関連証拠を提供してください。一旦確認がとりたいとされると、当サイトは直ちに侵害される内容を削除します。

おすすめ