Javaのシングルトンクラスにおけるスレッドセーフティー
シングルトンは、アプリケーションによって作成されるオブジェクトを制限するために最も広く使用される生成型のデザインパターンの一つです。もしマルチスレッド環境で使用する場合、シングルトンクラスのスレッドセーフ性は非常に重要です。実世界のアプリケーションでは、データベース接続や企業情報システム(EIS)のようなリソースが限られており、リソースの枯渇を避けるために賢く使用する必要があります。これを実現するために、シングルトンデザインパターンを実装できます。リソースのためのラッパークラスを作成し、ランタイムで作成されるオブジェクトの数を1つに制限します。
Javaでのスレッドセーフなシングルトン
-
- new演算子を使用しての新しいオブジェクト作成を避けるために、プライベートコンストラクタを作成します。
-
- 同じクラスのプライベートな静的インスタンスを宣言します。
- シングルトンクラスのインスタンス変数を返すパブリックな静的メソッドを提供します。変数が初期化されていない場合は初期化し、そうでなければ単純にインスタンス変数を返します。
上記の手順を使用して、以下のようなシングルトンクラスを作成しました。ASingleton.java
package com.scdev.designpatterns;
public class ASingleton {
private static ASingleton instance = null;
private ASingleton() {
}
public static ASingleton getInstance() {
if (instance == null) {
instance = new ASingleton();
}
return instance;
}
}
上記のコードでは、getInstance()メソッドはスレッドセーフではありません。複数のスレッドが同時にアクセスすることができます。インスタンス変数が初期化されていない最初のいくつかのスレッドでは、複数のスレッドがifループに入り、複数のインスタンスを作成することがあります。これは、シングルトンの実装を壊す可能性があります。
シングルトンクラスにおいてスレッドセーフを実現する方法
スレッドセーフティを確保する方法には3つあります。
-
- クラスの読み込み時にインスタンス変数を作成します。
- 利点:
- Thread safety without synchronization
- Easy to implement
デメリット:
- Early creation of resource that might not be used in the application.
- The client application can’t pass any argument, so we can’t reuse it. For example, having a generic singleton class for database connection where client application supplies database server properties.
-
- getInstance()メソッドを同期化する。
- 利点:
- Thread safety is guaranteed.
- Client application can pass parameters
- Lazy initialization achieved
デメリット:
- Slow performance because of locking overhead.
- Unnecessary synchronization that is not required once the instance variable is initialized.
-
- if文の内部で同期化ブロックとvolatile変数を使用すること
- 利点:
- Thread safety is guaranteed
- Client application can pass arguments
- Lazy initialization achieved
- Synchronization overhead is minimal and applicable only for first few threads when the variable is null.
欠点:
- Extra if condition
3つのスレッドセーフの実現方法を見て、その中で3番目が最も良い選択肢だと考えます。その場合、修正後のクラスは以下のようになります。
package com.scdev.designpatterns;
public class ASingleton {
private static volatile ASingleton instance;
private static Object mutex = new Object();
private ASingleton() {
}
public static ASingleton getInstance() {
ASingleton result = instance;
if (result == null) {
synchronized (mutex) {
result = instance;
if (result == null)
instance = result = new ASingleton();
}
}
return result;
}
}
ローカル変数のresultは不必要に思えますが、それはコードのパフォーマンスを向上させるために存在しています。インスタンスが既に初期化されている場合(ほとんどの場合)、volatileフィールドは1回だけアクセスされます(”return instance;”ではなく”return result;”によるため)。これにより、メソッド全体のパフォーマンスが最大25%向上する場合があります。もし、これを達成するためにより良い方法があると思われるか、もしくは上記の実装におけるスレッドセーフが危険にさらされていると考える場合は、コメントを残して共有してください。
ボーナスのヒント
Stringは、synchronizedキーワードと一緒に使用するのにはあまり適していません。なぜなら、文字列は文字列プールに保存されており、他のコードで使用されている可能性がある文字列をロックしたくないからです。そのため、Object変数を使用しています。Javaでの同期とスレッドの安全性についてもっと学びましょう。
当社のGitHubリポジトリから、さらに多くのJavaの例をチェックアウトすることができます。