ホームページ Java &#&チュートリアル Java でのシングルトンの実装の難しさ

Java でのシングルトンの実装の難しさ

Dec 05, 2016 am 11:34 AM
java

シングルトン パターンを実装する簡単で効率的な方法はありますが、あらゆる状況下でシングルトンの整合性を保証する方法はありません。

シングルトン パターンは、クラスが 1 回だけインスタンス化され、グローバルまたはシステム全体のコンポーネントを表すために使用されることを意味します。シングルトン パターンは、ロギング、ファクトリ、ウィンドウ マネージャー、プラットフォーム コンポーネント管理などによく使用されます。シングルトンパターンは一度実装すると変更やオーバーロードが難しく、テストケースの書き方の難しさやコード構造の悪さなどの問題が発生するため、できるだけ避けるべきだと思います。また、次の記事のシングルトン パターンは安全ではありません。

シングルトン パターンをより適切に実装する方法の研究に多くのエネルギーが費やされていますが、シンプルで効率的な実装方法があります。ただし、あらゆる状況下でシングルトンの整合性を保証する唯一の方法はありません。以下をお読みになり、同意するかどうかを確認してください。

最終フィールド

このメソッドはコンストラクターをプライベート化し、パブリック静的最終オブジェクトを提供します:

public class FooSingleton {    
    public final static FooSingleton INSTANCE = new FooSingleton();   
    private FooSingleton() { }    
    public void bar() { }}
ログイン後にコピー

クラスがロードされると、静的オブジェクトが初期化され、この時点でプライベート コンストラクターが最初と最後の転送に使用されます。クラスが初期化される前に複数のスレッドがこのクラスを呼び出した場合でも、JVM はスレッドが実行を継続するときにクラスが完全に初期化されていることを保証できます。ただし、リフレクションと setAccessible(true) メソッドを使用すると、他の新しいインスタンスを作成することができます:

Constructor[] constructors = FooSingleton.class.getDeclaredConstructors();
Constructor constructor = constructors[0];
constructor.setAccessible(true);
FooSingleton spuriousFoo = (FooSingleton) constructor.newInstance(new Object[0]);
ログイン後にコピー

コンストラクターを複数回呼び出す必要がないように変更する必要があります。たとえば、呼び出されたときに例外をスローします。また。 FooSingleton コンストラクターを次のように変更すると、そのような攻撃を防ぐことができます:

public class FooSingleton2 {    
private static boolean INSTANCE_CREATED;    
public final static FooSingleton2 INSTANCE = new FooSingleton2();    
private FooSingleton2() {        
    if (INSTANCE_CREATED) {            
        throw new IllegalStateException("You must only create one instance of this class");        
     } else {            
         INSTANCE_CREATED = true;        
     }    
  }    
      public void bar() { }
}
ログイン後にコピー

これはより安全に見えますが、実際には、新しいインスタンスを作成するのと同じくらい簡単です。 INSTANCE_CREATED フィールドを変更して、同じトリックをもう一度実行するだけです:

Field f = FooSingleton2.class.getDeclaredField("INSTANCE_CREATED");
f.setAccessible(true);
f.set(null, false);
Constructor[] constructors = FooSingleton2.class.getDeclaredConstructors();
Constructor constructor = constructors[0];
constructor.setAccessible(true);
FooSingleton2 spuriousFoo = (FooSingleton2) constructor.newInstance(new Object[0]);
ログイン後にコピー

私たちが講じた予防措置はバイパスされる可能性があるため、この解決策は実現できません。

静的ファクトリー

このメソッドを使用すると、パブリック メンバーは静的ファクトリーと同様になります:

public class FooSingleton3 {    
  public final static FooSingleton3 INSTANCE = new FooSingleton3();    
  private FooSingleton3() { }    
  public static FooSingleton3 getInstance() { return INSTANCE; }    
  public void bar() { }
}
ログイン後にコピー

getInstance() メソッドは常に同じオブジェクト参照を返します。この解決策では反射を防ぐことはできませんが、それでもいくつかの利点があります。たとえば、API を変更せずにシングルトンの実装を変更できます。 getInstance() は、ほぼすべてのシングルトン実装に出現し、これが実際にはシングルトン パターンであることを示します。

遅延読み込みシングルトン モード

(翻訳者注: ソフトウェア工学では、Initialization-on-demand というイディオムは遅延読み込みシングルトン モードを指します。Wikipedia を参照してください)

できるだけ遅延させたい場合は、シングルトンを作成するには(遅延読み込み)、getInstance() メソッドが初めて呼び出されたときに、遅延初期化メソッドを使用してシングルトンをスレッドセーフに作成できます。クラスが初めて参照されるときにシングルトンを作成する以前のソリューション (ハングリー スタイルのロード) と比較すると、これは改善です。次のとおりです:

public class FooSingleton4 {    
private FooSingleton4() {    
}    
public static FooSingleton4 getInstance() {       
 return FooSingleton4Holder.INSTANCE;    
 }    
private static class FooSingleton4Holder {       
 private static final FooSingleton4 INSTANCE = new FooSingleton4();    
 }
}
ログイン後にコピー

シリアル化には注意してください

シングルトンがシリアル化を実装すると、別の脅威に直面します。したがって、すべてのフィールドを一時的として宣言し (シリアル化されないように)、一意のインスタンス INSTANCE への参照を返すカスタム readResolve() メソッドを提供する必要があります。

列挙

ここでは、単一インスタンス INSTANCE のコンテナとして列挙を使用します:

public enum FooEnumSingleton {
    INSTANCE;    
    public static FooEnumSingleton getInstance() { 
    return INSTANCE; 
    }    
    public void bar() { }
}
ログイン後にコピー

Java 言語仕様 8.9 によると、「Enum の最終的な複製メソッドは列挙が決して複製できないことを保証し、その特別なシリアル化メカニズムは列挙が複製できないことを保証します」同時に、列挙型をインスタンス化するためにリフレクションを使用することも禁止されています。これにより、列挙型定数の外に他の同様の列挙型インスタンスが存在しないことが保証されます。そして反射攻撃。この一節を初めて見たとき、私はすぐにそれが間違いであることを証明したいと思いました。次のコードに示すように、これらの保護を回避するのは簡単です:

Constructor con = FooEnumSingleton.class.getDeclaredConstructors()[0];
 Method[] methods = con.getClass().getDeclaredMethods();
 for (Method method : methods) {
     if (method.getName().equals("acquireConstructorAccessor")) {
         method.setAccessible(true);
         method.invoke(con, new Object[0]);
     }
  }
  Field[] fields = con.getClass().getDeclaredFields();
  Object ca = null;  for (Field field : fields) {
      if (field.getName().equals("constructorAccessor")) {
          field.setAccessible(true);
          ca = field.get(con);
      }
  }  Method method = ca.getClass().getMethod("newInstance", new Class[]{Object[].class});
  method.setAccessible(true);
  FooEnumSingleton spuriousEnum = (FooEnumSingleton) method.invoke(ca, new Object[]{new Object[]{"SPURIOUS_INSTANCE", 1}});
  printInfo(FooEnumSingleton.INSTANCE);
  printInfo(spuriousEnum);
}private static void printInfo(FooEnumSingleton e) {
    System.out.println(e.getClass() + ":" + e.name() + ":" + e.ordinal());
}
ログイン後にコピー

このコードを実行すると、次の結果が得られます:

class com.blogspot.minborgsjavapot.singleton.FooEnumSingleton:INSTANCE:0
class com.blogspot.minborgsjavapot.singleton.FooEnumSingleton:SPURIOUS_INSTANCE:1
ログイン後にコピー

列挙型の欠点は、すでに継承されているため、別の基本クラスから継承できないことです。 java.lang .Enum.この種の継承をシミュレートしたい場合は、私の他の記事で紹介したミックスイン パターンを参照してください。

列挙型の利点の 1 つは、後で「デュアルトン」または「トリングルトン」が必要になった場合に、新しい列挙型インスタンスを追加するだけで済むことです。たとえば、シングルトン キャッシュを作成した後、そのキャッシュに複数のレイヤーを導入することもできます。

結論

シングルトンに対するこれらの保護をバイパスするのは簡単ではありませんが、確実な解決策は実際にはありません。より良い解決策があれば、お気軽にお知らせください。

列挙は、シングルトン パターンを実装する簡単かつ効率的な方法です。継承または遅延ロードが必要な場合は、遅延初期化が良い選択です。

シングルトンの幸運を祈ります!

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

AI Hentai Generator

AI Hentai Generator

AIヘンタイを無料で生成します。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

Javaの完全数 Javaの完全数 Aug 30, 2024 pm 04:28 PM

Java における完全数のガイド。ここでは、定義、Java で完全数を確認する方法、コード実装の例について説明します。

Java の乱数ジェネレーター Java の乱数ジェネレーター Aug 30, 2024 pm 04:27 PM

Java の乱数ジェネレーターのガイド。ここでは、Java の関数について例を挙げて説明し、2 つの異なるジェネレーターについて例を挙げて説明します。

ジャワのウェカ ジャワのウェカ Aug 30, 2024 pm 04:28 PM

Java の Weka へのガイド。ここでは、weka java の概要、使い方、プラットフォームの種類、利点について例を交えて説明します。

Javaのスミス番号 Javaのスミス番号 Aug 30, 2024 pm 04:28 PM

Java のスミス番号のガイド。ここでは定義、Java でスミス番号を確認する方法について説明します。コード実装の例。

Java Springのインタビューの質問 Java Springのインタビューの質問 Aug 30, 2024 pm 04:29 PM

この記事では、Java Spring の面接で最もよく聞かれる質問とその詳細な回答をまとめました。面接を突破できるように。

Java 8 Stream Foreachから休憩または戻ってきますか? Java 8 Stream Foreachから休憩または戻ってきますか? Feb 07, 2025 pm 12:09 PM

Java 8は、Stream APIを導入し、データ収集を処理する強力で表現力のある方法を提供します。ただし、ストリームを使用する際の一般的な質問は次のとおりです。 従来のループにより、早期の中断やリターンが可能になりますが、StreamのForeachメソッドはこの方法を直接サポートしていません。この記事では、理由を説明し、ストリーム処理システムに早期終了を実装するための代替方法を調査します。 さらに読み取り:JavaストリームAPIの改善 ストリームを理解してください Foreachメソッドは、ストリーム内の各要素で1つの操作を実行する端末操作です。その設計意図はです

Java での日付までのタイムスタンプ Java での日付までのタイムスタンプ Aug 30, 2024 pm 04:28 PM

Java での日付までのタイムスタンプに関するガイド。ここでは、Java でタイムスタンプを日付に変換する方法とその概要について、例とともに説明します。

カプセルの量を見つけるためのJavaプログラム カプセルの量を見つけるためのJavaプログラム Feb 07, 2025 am 11:37 AM

カプセルは3次元の幾何学的図形で、両端にシリンダーと半球で構成されています。カプセルの体積は、シリンダーの体積と両端に半球の体積を追加することで計算できます。このチュートリアルでは、さまざまな方法を使用して、Javaの特定のカプセルの体積を計算する方法について説明します。 カプセルボリュームフォーミュラ カプセルボリュームの式は次のとおりです。 カプセル体積=円筒形の体積2つの半球体積 で、 R:半球の半径。 H:シリンダーの高さ(半球を除く)。 例1 入力 RADIUS = 5ユニット 高さ= 10単位 出力 ボリューム= 1570.8立方ユニット 説明する 式を使用してボリュームを計算します。 ボリューム=π×R2×H(4

See all articles