時々、特定のクラスが 1 つのオブジェクトしか生成できないようにしたい場合があります。例えば、学校の校長は 1 人しかいない場合などです。
初めに#
クラスが 1 つのインスタンスしか持たないようにするには、コンストラクタを非公開にする必要があります:private Something(){}
そして、外部からオブジェクトを生成 / 返すためのメソッドを提供します。以下の実装を得ることができます。
public class Something {
private static Something INSTANCE = null;
private Something(){}
public static Something getINSTANCE() {
if (INSTANCE == null){
INSTANCE = new Something();
}
return INSTANCE;
}
}
これはシングルトンパターンの簡単な実装です。getINSTANCE()
が呼び出されるたびに、INSTANCE == null
をチェックする必要があることに気づきます。
したがって、以下のようにも書くことができます(シングルトンオブジェクトが十分に小さく、実行中に常に必要な場合):
public class Something {
private static Something INSTANCE = new Something();
private Something(){}
public static Something getINSTANCE() {
return INSTANCE;
}
}
マルチスレッド環境でのシングルトンパターン#
上記のシングルトンパターンの実装はマルチスレッド環境では安全ではありません。INSTANCE
の値が変更されないことを保証できません。スレッドの問題を解決する方法は非常に簡単で、ロックを追加することです。
例えば、getINSTANCE
メソッドにロックを追加します。
public static synchronized Something getINSTANCE() {
if (INSTANCE == null){
INSTANCE = new Something();
}
return INSTANCE;
}
または、null をチェックするときにロックを追加します。
public static Something getINSTANCE() {
synchronized (Something.class){
if (INSTANCE == null){
INSTANCE = new Something();
}
}
return INSTANCE;
}
しかし、これには 1 つの問題があります。インスタンスを取得するたびにロック操作が行われ、リソースが消費されます。このメソッドが頻繁に使用される場合、パフォーマンスの問題が発生する可能性があります。
コードを以下のように変更します。
public static Something getINSTANCE() {
if (INSTANCE == null){
synchronized (Something.class){
if (INSTANCE == null){
INSTANCE = new Something();
}
}
}
return INSTANCE;
}
このように書くと、INSTANCE
が null の場合にのみロックが行われます。つまり、1 回だけ効果があります。
列挙型を使用したシングルトンの実装#
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
列挙型を使用してシングルトンを実装すると、クラスが実際に使用されるまでオブジェクトがロードされません(クラスのロードメカニズムを利用)。ロードプロセスは同期されており、スレッドセーフです。
前述のシングルトンは、シリアライズおよびデシリアライズに対しては安全ではありませんが、列挙型では、列挙型オブジェクトのデシリアライズができないように明示的に規定されています。
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {
// 省略
// 列挙型オブジェクトのデシリアライズは許可されていません
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
throw new InvalidObjectException("can't deserialize enum");
}
// 列挙型オブジェクトのデシリアライズは許可されていません
private void readObjectNoData() throws ObjectStreamException {
throw new InvalidObjectException("can't deserialize enum");
}
// 列挙型クラスにはfinalizeメソッドを持つことはできません。サブクラスはこのメソッドをオーバーライドできません。インスタンスのオブジェクトの一意性を保証します。
protected final void finalize() { }
}