Table of Contents
導入: 多層エンジニアリングアプリケーションにおける単トンパターン
シングルトンパターンは、ソフトウェアエンジニアリングにおける最も広く使用されている創造的設計パターンの1つです。クラスがインスタンスを1つだけ持つことを確実にし、そのインスタンスへのアクセスのグローバルポイントを提供します。単一スレッドアプリケーションでは、シングルトンを実装するのは簡単です。コンストラクタをプライベートにし、単一のインスタンスが作成されるか、またはラジリーを生成する静的メソッドを提供します。ただし、組込みシステム、高周波取引条件、リアルタイムの監視、および複数のセキュリティシステムなど、マルチスレッドエンジニアリングアプリケーションでは、複数のレベルのアプリケーションで、複雑なエラーやエラーを解決できます。
この記事では、マルチスレッド環境で単調パターンを実装する際に、最も一般的な間違いの開発者が作ることを調べ、根本的な原因を説明し、それらを避けるための最良の慣行とパターンの包括的なセットを提供します。 また、Javaの実用的なコード例、C++とC#の同等のパターンを参照し、さらに外部リソースを読んでください。
シングルトン実装における共通ミス
同時並列システムで単トンを実装する際に経験豊富な開発者もトラップに陥る可能性があります。以下は最も頻繁にエラーです。
1. 建設家プライベートをしない
任意の単調の基礎は、外部のインスタンス化を防ぐプライベートコンストラクターです。コンストラクターがアクセス可能(public、protected、またはpackage-private)であれば、任意のスレッドは、シングルトン契約を破る新しいインスタンスを作成することができます。マルチスレッドコードでは、クラスが再ファクタリングされ、コンストラクタの可視性が誤って変更されるとき、またはクラスがサブクラスがサブクラストされるとき(シングルトンをサブクラストする場合、一般的には、控えめな行動)、およびプライベートコンストラクタの動作を宣言する必要があります。
2. 糸の安全を扱いにくい
単一スレッド環境では、単純なレイジー初期化がうまく機能します。
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
しかし、複数のスレッドアプリケーションでは、パターンを違反して、複数のスレッドが[]のチェックを同時に入力できます。各スレッドは、その独自のオブジェクトを作成するために進みます。これは、複数のインスタンスで結果的にのraceconditionの古典的な]]です。そして、意図的な状態やリソースリークにつながることができます。
3. 適切な同時性のない怠惰な初期化を使用して下さい
スレッドの安全の必要性を認識する開発者でさえ、しばしば同期を積極的に追加します。例えば、[メソッド全体を同期させるが、パフォーマンスボトルネックを導入します。
public static synchronized Singleton getInstance() { ... }
にすべての呼び出しは、インスタンスが既に作成されても、ロックを受け取り、解放します。 高度コンテンツのシナリオでは、このオーバーヘッドは、重度スループットを劣化させる可能性があります。 より良いアプローチは、 ] をダブルチェックロック を使用することです。 (以下に示す)、そのパターンは正しく実装されていない場合は下落しています。
4. 過度な同期
同期は、多くの形態で来ます: 方法, [] ブロック, ], ], など. 過同期 - 粗粒仕上げロックを応用して、微粒仕上げ制御が利用可能であるとき、 - 不要な分裂を除去する。 いくつかのエンジニアリングアプリケーション(例えば、厳格な遅延を伴うリアルタイムシステム)では、ナノヘッドが少ない場合でも、いくつかの安全セクションが重要である。
5. []の揮発性のキーワードを無視する
Java、C#、C++()などの言語では、volatile]キーワード(または同等)は、マルチスレッドコードで正しい可視性のために不可欠です。 それなしで、コンパイラまたはCPUは指示を並べ替え、 1つのスレッドで行われた変更は、別のスレッドに表示されないことがあります。 両面ロックパターンでは、シングルトンインスタンスを:11]として宣言することに失敗すると、ほとんどが、ほとんどが誤って、ほとんどが見えない動作を予測できなくなります。
スレッドセーフシングルトンの実装に最適なプラクティス
これらの落とし穴を避けるために、これらの実証済みの戦略に従ってください。各アプローチは、スレッドの安全性、パフォーマンス、シンプルさに対処します。
民間コンストラクタと静的インスタンス
初期化戦略に関係なく、コンストラクタはプライベートでなければなりません。単調インスタンスは静的フィールドに格納する必要があります。コンストラクタを任意の方法で露出し、サブクラスを防止するためにJava(または)でクラスを作ることを検討しないでください。
必要なときにのみ同期ブロックを使用する
怠惰な初期化のために、二重チェックされた錠パターンは同期の頭上を削減します:
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
Singleton result = instance; // Local variable for performance
if (result == null) {
synchronized (Singleton.class) {
result = instance;
if (result == null) {
instance = result = new Singleton();
}
}
}
return result;
}
}
このコードでは、sychronizedブロックの外側にあるチェックは、インスタンスが既に存在するときにロックオーバーヘッドを回避します。内部チェックは、スレッドがインスタンスを作成するだけであることを保証します。 [キーワードは、指示のリオーダーを防ぎ、割り当て[が他のスレッドに完全に表示されていることを保証します。 パフォーマンスのためのローカル変数のインスタンスをキャッシュすることに注意してください。 このパターンは、Java 5 + (適切なメモリモデルと) および C++ 同様の動作をC++で正しく動作させる(C++)、C++ およびC++ ) 同様に動作するようにします。
エイジャー・イニシャル化
シングルトンが常に必要とされ、作成が安い場合、エイジャー初期化は最も簡単なスレッドセーフなアプローチです。
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
クラス読み込みは、JVM によって非本質的に同期されるため、追加のコオシネーションは必要ありません。しかし、これはクラスロード時にインスタンスを作成します。これは、リソースの制約システムで望ましくない、またはシングルトンがまだ利用されていないランタイム設定に依存している場合です。
静的ホルダーパターン(初期化オンデマンド)
このパターンは、明示的な同期なしでスレッドの安全と怠惰な初期化を組み合わせます。
public class Singleton {
private Singleton() {}
private static class Holder {
static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
[クラスはが最初に呼び出されたときだけ読み込まれ、JVMはクラスの読み込み中に静的フィールドの安全な出版物を保証します。 これは、Javaシングルトンの最もエレガントなソリューションとして広く評価されています。
Enum-Based シングルトン (Java)
ジョシュア・ブロッハの] - 効果的なJava[] - enumを使用して推奨します。
public enum Singleton {
INSTANCE;
// methods and fields
}
Enum の定数は、暗黙 であり、Java の言語は、列化や反射攻撃でも、インスタンスが一度だけ作成されることを保証しています。 これは、スレッドセーフで簡潔です。 しかし、列はクラスを拡張できません(インターフェイスのみを実装)、すべてのユースケースには適していません。
C++とC#の同等のパターン
C++では、Meyerのシングルトン(ローカル静的初期化)はC++11以降はスレッドセーフです。
Singleton& getInstance() {
static Singleton instance;
return instance;
}
C#では、[クラスは、組み込みのスレッドセーフなレイジー初期化を提供します。
public class Singleton {
private static readonly Lazy<Singleton> _lazy =
new Lazy<Singleton>(() => new Singleton());
public static Singleton Instance => _lazy.Value;
}
エンジニアリングアプリケーションのテストと検討
エンジニアリングアプリケーションでは、シングルトンパターンは、ハードウェアドライバ、設定設定設定、スレッドプール、またはロギングサービスなどの共有リソースを頻繁に管理します。 複数のスレッドテストでそのような単トンをテストするには、慎重に設計する必要があります。 以下を検討してください。
- []シングルトンのテストを有効に]をインスタンスをリセットする方法を提供することで(例えば、保護されたテストでのみ使用されるメソッド)、またはインターフェイスを介して依存関係を注入することによって。 多くの近代的なアプリケーションは、ライフサイクルを管理する依存症の注入フレームワークの支持で単トンを完全に回避します。
- リアルタイムまたは高周波システムにおけるパフォーマンスプロファイリング[:同期のオーバーヘッドを測定します。 場合によっては、 (C#) または] (C++) を使用してロックフリーのシングルトンが正当化されることがあります。
- []分散システム[]]は、プロセス全体ではなく、プロセスごとに一意に一意に単トンを必要とする。 クラスター全体シングルトンが必要な場合は、外部のコーディネート(データベース、ZooKeeper、またはリーダーの選挙など)を使用してください。
- ] のリフレクションとシリアライズ は、シングルトンを破ることができます。Javaのシリアライズで] を使用し、コンストラクタで例外を投げて反射インスタンス化を防ぎます。] は既に設定されています。
コンテンツ
シングルトンパターンは、ソフトウェアエンジニアのツールボックスに貴重なツールを残していますが、マルチスレッド環境での実装では、細部への厳格な注意が必要です。非プライベートコンストラクタ、欠如の同期、不適切な揮発性使用、および過同期などの一般的な間違いを理解し、回避することにより、開発は、堅牢で高性能なシングルトンを生成することができます。ダブルチェックされたロックパターン、静的ホルダーパターン、およびJavaのシングルトンは、各々の作業を簡素化し、C#の効率性を簡素化します。
さらなる研究については、以下のリソースを参照してください。
- Wikipedia:シングルトンパターン
- [Oracle Javaシングルトンチュートリアル[]
- []「ダブルチェックロックは壊れています」宣言[
- Microsoft .NET シングルトンパターン[
最終的には、最も優れたシングルトン実装は、要件の最も簡単です。 疑わしいときは、エイジャー初期化や静的なホルダーパターンを好む、そして常にコンカレントユニットテストを書いて、コンパニオンの下で正しい精度を検証します。