JAVA単一例(Singleton)で実現されるいくつかの方法(マルチスレッドセキュリティ)
4539 ワード
主に二つに分けられます。直接初期化 遅延初期化 直接初期化
直接初期化final静的なメンバー
スレッド安全:JVMはfinal静的なメンバーが一回だけ初期化されることを保証します。
公有静的メンバは、finalドメインであり、直接メンバを参照して、単一の例を取得する。
柔軟性が提供され、APIを変更しない前提で、このクラスは単一の例であるべきかどうかの考え方を変えることができる。例えば、この方法を呼び出すスレッドごとにユニークな例を返す(
エニュメレート・タイプの性質によって保証されるエニュメレーション定数
静的工場法に直接
利点:同期方法のオーバーヘッドを回避する。
INSTANCEは、volatile修饰子を使用して、JVMのリアルタイムコンパイラによる
JVMでnew操作は次の3つのことをしました。は、 を割り当てる。は、 を初期化する。は、ステップ1で割り当てられたメモリ空間 にINSTANCEを向ける。
JVMには命令並び替えの最適化があるので、上記第2ステップと第3ステップの順序は保証できない(1−3−3または1−3−2)。ステップが1−3−2であれば、スレッドAが第3ステップまで実行されたと仮定するが、第2ステップはまだ実行されていない。このとき、スレッドB呼び出し
直接初期化final静的なメンバー
スレッド安全:JVMはfinal静的なメンバーが一回だけ初期化されることを保証します。
公有静的メンバは、finalドメインであり、直接メンバを参照して、単一の例を取得する。
/**
* final
*
*/
public class Singleton1 {
public static final Singleton1 INSTANCE = new Singleton1();
/**
* private, new
*/
private Singleton1() {}
public void someMethod() {}
public static void main(String[] args) {
Singleton1.INSTANCE.someMethod();
}
}
公有のメンバは静的工場法であり、この方法により単一の例を取得する。柔軟性が提供され、APIを変更しない前提で、このクラスは単一の例であるべきかどうかの考え方を変えることができる。例えば、この方法を呼び出すスレッドごとにユニークな例を返す(
ThreadLocal
)。/**
* ,
*/
public class Singleton2 {
private static final Singleton2 INSTANCE = new Singleton2();
/**
* private, new
*/
private Singleton2() {}
/**
*
* @return Singleton2
*/
public static Singleton2 getInstance() { return INSTANCE; }
public void someMethod() {}
public static void main(String[] args) {
Singleton2.getInstance().someMethod();
}
}
単一要素を含むエニュメレート・タイプ(enum)エニュメレート・タイプの性質によって保証されるエニュメレーション定数
INSTANCE
は、唯一の例である。/**
*
*
*/
public enum EnumSingleton {
INSTANCE;
public void someMethod() { /** .... */}
public static void main(String[] args) {
EnumSingleton.INSTANCE.someMethod();
}
}
遅延初期化静的工場法に直接
synchronized
を加える。短所:呼び出しごとにスレッドオーバーヘッドがあります。/**
*
*/
public class LazyInitSingleton1 {
private static LazyInitSingleton1 INSTANCE;
/**
* private, new
*/
private LazyInitSingleton1() {}
/**
*
*
*
* @return LazyInitSingleton1
*/
public synchronized static LazyInitSingleton1 getInstance() {
if (INSTANCE == null) {
INSTANCE = new LazyInitSingleton1();
}
return INSTANCE;
}
public void someMethod() {}
public static void main(String[] args) {
Singleton2.getInstance().someMethod();
}
}
lazy initialization holder classモードです。(「Effective Java」71条参照:遅延初期化を慎む利点:同期方法のオーバーヘッドを回避する。
getInstance
が第1回起動されたとき、SingletonHolder.field
を読み出し、SingletonHolder
類が初期化される。/**
* lazy initialization holder class
*
*/
public class LazyInitSingleton2 {
private static class SingletonHolder {
static final LazyInitSingleton2 field = computeFieldValue();
private static LazyInitSingleton2 computeFieldValue() {
return new LazyInitSingleton2();
}
}
private LazyInitSingleton2() {}
public static LazyInitSingleton2 getInstance() {
return SingletonHolder.field;
}
}
二重検出で同期方法のオーバーヘッドを低減します。INSTANCEは、volatile修饰子を使用して、JVMのリアルタイムコンパイラによる
INSTANCE = new LazyInitSingleton3()
操作の命令の並び替えを防止します。/**
* , ,
*/
public class LazyInitSingleton3 {
/**
* : volatile
*/
private static volatile LazyInitSingleton3 INSTANCE;
private LazyInitSingleton3() {}
public static LazyInitSingleton3 getInstance() {
// , INSTANCE ,
// ,
if (INSTANCE == null) {
// ( getInstance, INSTANCE == NULL)
synchronized (LazyInitSingleton3.class) {
// ,
// INSTANCE,
// INSTANCE != NULL,
if (INSTANCE == null) {
INSTANCE = new LazyInitSingleton3();
}
}
}
return INSTANCE;
}
}
どうしてINSTANCEはvolatile
修繕符を使いますか?JVMでnew操作は次の3つのことをしました。
new
が必要なオブジェクトLazyInitSingleton3
にメモリ空間LazyInitSingleton3
の構成関数を呼び出してオブジェクトJVMには命令並び替えの最適化があるので、上記第2ステップと第3ステップの順序は保証できない(1−3−3または1−3−2)。ステップが1−3−2であれば、スレッドAが第3ステップまで実行されたと仮定するが、第2ステップはまだ実行されていない。このとき、スレッドB呼び出し
getInstance()
はINSTANCE
が非空であることを発見する(初期化されていない)。INSTANCE
に直接戻り、その後、スレッドBがINSTANCE
に対して動作すると、エラーが発生する可能性がある(オブジェクトが初期化されていないため)。volatile
修飾子は、命令の並び替えの最適化を防止し、実行順序は1−2−3であることを保証する。