11

ここでの最近の質問には、同期なしでシングルトンを実装するための次のコード(まあ、これに似ています)がありました。

public class Singleton {
    private Singleton() {}
    private static class SingletonHolder { 
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

今、私はこれが何をしているのか理解していると思います。インスタンスはstatic finalであるため、スレッドが呼び出されるずっと前にビルドされgetInstance()、同期の実際の必要はありません。

getInstance()同期が必要になるのは、2つのスレッドが同時に呼び出そうとした場合のみです(そして、そのメソッドは、"static final"時間ではなく最初の呼び出しで構築を行いました)。

したがって、私の質問は基本的に次のとおりです。なぜ、次のようなシングルトンの怠惰な構築を好むのでしょうか。

public class Singleton {
    private Singleton() {}
    private static Singleton instance = null;
    public static synchronized Singleton getInstance() {
        if (instance == null)
            instance = new Singleton();
        return instance;
    }
}

私の唯一の考えは、このstatic finalメソッドを使用すると、C++の静的初期化順序の大失敗のようにシーケンスの問題が発生する可能性があるということでした。

まず、Javaには実際この問題がありますか?クラス内の順序が完全に指定されていることは知っていますが、クラス間で一貫した順序が保証されていますか(クラスローダーなど)?

第二に、順序一貫している場合、なぜ怠惰な構築オプションが有利になるのでしょうか?

4

11 に答える 11

13

今、私はこれが何をしているのか理解していると思います。インスタンスは静的finalであるため、スレッドがgetInstance()を呼び出すずっと前にビルドされ、同期の実際の必要はありません。

完全ではありません。SingletonHolderこれは、クラスが初期化されたときに構築されます。これは、最初getInstanceに呼び出されたときに発生します。クラスローダーには個別のロックメカニズムがありますが、クラスがロードされた後はそれ以上のロックは必要ないため、このスキームは複数のインスタンス化を防ぐのに十分なロックを実行します。

まず、Javaには実際にこの問題がありますか?クラス内の順序が完全に指定されていることは知っていますが、クラス間で一貫した順序が保証されていますか(クラスローダーなど)?

Javaには、クラスの初期化サイクルによって、あるクラスが初期化される前に(技術的には、すべての静的初期化ブロックが実行される前に)別のクラスの静的ファイナルを監視する可能性があるという問題があります。

検討

class A {
  static final int X = B.Y;
  // Call to Math.min defeats constant inlining
  static final int Y = Math.min(42, 43);
}

class B {
  static final int X = A.Y;
  static final int Y = Math.min(42, 43);
}

public class C {
  public static void main(String[] argv) {
    System.err.println("A.X=" + A.X + ", A.Y=" + A.Y);
    System.err.println("B.X=" + B.X + ", B.Y=" + B.Y);
  }
}

Cプリントの実行

A.X=42, A.Y=42
B.X=0, B.Y=42

しかし、あなたが投稿したイディオムでは、ヘルパーとシングルトンの間にサイクルがないため、遅延初期化を好む理由はありません。

于 2011-07-07T06:38:29.553 に答える
3

今、私はこれが何をしているのか理解していると思います。インスタンスは静的finalであるため、スレッドがgetInstance()を呼び出すずっと前にビルドされ、同期の実際の必要はありません。

いいえ。クラスは、初めてSingletonHolder呼び出すときにのみロードされます。オブジェクトは、完全に構築された後にのみ他のスレッドに表示されます。このような遅延初期化はと呼ばれます。SingletonHolder.INSTANCEfinalInitialization on demand holder idiom

于 2011-07-07T06:39:10.203 に答える
1

効果的なJavaで、Joshua Blochは、「このイディオム、クラスが使用されるまで初期化されないという保証を利用しています[ JLS、12.4.1 ]」と述べています。

于 2011-07-07T06:45:42.673 に答える
1

あなたが説明したパターンは2つの理由で機能します

  1. クラスは最初にアクセスされたときにロードおよび初期化されます(ここではSingletonHolder.INSTANCEを介して)
  2. クラスのロードと初期化はJavaではアトミックです

したがって、スレッドセーフで効率的な方法で遅延初期化を実行します。このパターンは、同期された遅延初期化に対するダブルロック(機能しない)ソリューションのより良い代替手段です。

于 2011-07-07T06:47:38.470 に答える
0

同期されたブロックやメソッドを作成する必要がないため、熱心に初期化します。これは主に、同期は一般的に高価であると考えられているためです

于 2011-07-07T06:35:26.047 に答える
0

最初の実装について少し注意してください。ここで興味深いのは、クラスの初期化が従来の同期の代わりに使用されることです。

クラスの初期化は、完全に初期化されていない限り(つまり、すべての静的初期化コードが実行されていない限り)、コードがクラスのいずれにもアクセスできないという点で非常に明確に定義されています。また、すでにロードされているクラスにはほぼゼロのオーバーヘッドでアクセスできるため、これにより、「同期」オーバーヘッドが実際のチェックが行われる場合(つまり、「クラスはロード/初期化されていますか?」)に制限されます。

クラスローディングメカニズムを使用することの1つの欠点は、それが壊れたときにデバッグするのが難しい可能性があることです。何らかの理由でSingletonコンストラクターが例外をスローした場合、最初の呼び出し元getInstance()はその例外を取得します(別の例外にラップされます)。

ただし、2番目の呼び出し元は、問題の根本的な原因を確認することNoClassDefFoundErrorはできません(単に、を取得します)。したがって、最初の発信者がどういうわけか問題を無視した場合、正確に何が悪かったのかを知ることはできません。

単に同期を使用する場合、2番目に呼び出されたものはSingleton再度インスタンス化を試み、おそらく同じ問題が発生します(または成功することさえあります!)。

于 2011-07-07T06:42:34.523 に答える
0

最初のバージョンのコードは、シングルトンを安全に怠惰に構築するための正しく最良の方法です。Javaメモリモデルは、INSTANCEが次のことを保証します。

  • クラスは最初に使用されたときにのみロードされるため、最初に実際に使用されたとき(つまりレイジー)にのみ初期化されます
  • クラスが使用可能になる前にすべての静的初期化が完了することが保証されているため、完全にスレッドセーフになるように1回だけ構築されます

バージョン1は、従うべき優れたパターンです。

編集済み
バージョン2はスレッドセーフですが、少し高価であり、さらに重要なことに、同時実行性/スループットを大幅に制限します

于 2011-07-07T06:45:07.627 に答える
0

クラスは、実行時にアクセスされるときに初期化されます。したがって、初期化順序はほとんど実行順序です。

ここでの「アクセス」とは、仕様で指定されている限定的なアクションを指します。次のセクションでは、初期化について説明します。

最初の例で起こっていることは同等です

public static Singleton getSingleton()
{
    synchronized( SingletonHolder.class )
    {
        if( ! inited (SingletonHolder.class) )
            init( SingletonHolder.class );
    } 
    return SingletonHolder.INSTANCE;
}

(初期化されると、同期ブロックは役に立たなくなります。JVMはそれを最適化します。)

意味的には、これは2番目のimplと同じです。これは、ダブルチェックロックであるため、「ダブルチェックロック」よりも優れているわけではありません。

クラスの初期化セマンティクスに便乗するため、静的インスタンスに対してのみ機能します。一般に、遅延評価は静的インスタンスに限定されません。セッションごとにインスタンスがあると想像してください。

于 2011-07-07T06:50:21.037 に答える
0

まず、Javaには実際にこの問題がありますか?クラス内の順序が完全に指定されていることは知っていますが、クラス間で一貫した順序が保証されていますか(クラスローダーなど)?

そうですが、C++よりも程度は低いです。

  • 依存関係のサイクルがない場合、静的初期化は正しい順序で行われます。

  • クラスのグループの静的初期化に依存サイクルがある場合、クラスの初期化の順序は不確定です。

  • ただし、Javaは、静的フィールドのデフォルトの初期化(null/ゼロ/false)が、コードがフィールドの値を確認する前に行われることを保証します。したがって、クラスは(理論的には)初期化の順序に関係なく正しいことを行うように作成できます。

第二に、順序が一貫している場合、なぜ怠惰な構築オプションが有利になるのでしょうか?

遅延初期化は、次のような多くの状況で役立ちます。

  • 初期化に、オブジェクトが実際に使用されない限り発生したくない副作用がある場合。

  • 初期化に費用がかかり、不必要に時間を無駄にしたくない場合...またはより重要なことをより早く実行したい場合(UIの表示など)。

  • 初期化が静的初期化時に利用できない状態に依存している場合。(ただし、遅延初期化がトリガーされたときに状態が使用できない可能性があるため、これには注意する必要があります。)

getInstance()同期メソッドを使用して遅延初期化を実装することもできます。getInstance()少し遅くなりますが、理解しやすくなります。

于 2011-07-07T07:06:56.940 に答える
0

私はあなたのコードスニペットには興味がありませんが、あなたの質問に対する答えがあります。はい、Javaには初期化順序の大失敗があります。相互に依存する列挙型に出くわしました。例は次のようになります。

enum A {
  A1(B.B1);
  private final B b;
  A(B b) { this.b = b; }
  B getB() { return b; }
}

enum B {
  B1(A.A1);
  private final A a;
  B(A a) { this.a = a; }
  A getA() { return a; }
}

重要なのは、インスタンスA.A1を作成するときにB.B1が存在している必要があるということです。また、A.A1を作成するには、B.B1が存在する必要があります。

私の実際のユースケースはもう少し複雑でした。列挙型間の関係は実際には親子であったため、1つの列挙型はその親への参照を返していましたが、その子の2番目の配列を返していました。子は列挙型のプライベート静的フィールドでした。興味深いことに、Windowsでの開発中はすべてが正常に機能していましたが、本番環境(Solaris)では、子配列のメンバーがnullでした。配列には適切なサイズがありましたが、配列がインスタンス化されたときに使用できなかったため、その要素はnullでした。

そのため、最初の呼び出しで同期された初期化が行われました。:-)

于 2011-07-14T08:10:36.637 に答える
0

Javaで唯一正しいシングルトーンは、クラスではなく列挙型で宣言できます。

public enum Singleton{
   INST;
   ... all other stuff from the class, including the private constructor
}

用途は次のとおりです。

Singleton reference1ToSingleton=Singleton.INST;    

他のすべての方法では、リフレクションによる繰り返しのインスタンス化、またはクラスのソースがアプリソースに直接存在する場合を除外しません。列挙型はすべてを除外します。(列挙型の最後のクローンメソッドは、列挙型定数がクローンされないことを保証します

于 2012-02-01T11:37:13.417 に答える