JAvaにおける単例モードのまとめ
4458 ワード
前に面接で、面接官にデザインパターンを聞かれ、単例パターンを知っていると自信を持っていました.そして、どの実現方法を知っているのか、一番いいと思う方法を教えてください.以前見たのは怠け者と餓漢モードではないかと思った.そして長所と短所を話したら、面接官にひどく軽蔑されて、この2つを知っていますか?そこで帰ってきたら、もう一度注文パターンをよく見て、まとめてみることにしました.
シングル・インスタンス・モードは、まずスレッドの安全を保証する必要があるため、すべてのスレッドが安全ではないシングル・インスタンス・モードは議論の範囲内ではありません.
1、怠け者モード
この書き方は確かにスレッドの安全を保証することができるが、効率は非常に低く、毎回synchronizedキーワード同期を使用する必要がある.しかし、実際には99%の場合は同期は必要ありません
2、餓漢モード
この方式はクラスローディングメカニズムに基づいてマルチスレッドの同期問題を回避するが、instanceはクラスローディング時にインスタンス化され、クラスローディングの原因は様々であるが、単例モードではgetInstanceメソッドを呼び出すことが多いが、他の方式(または他の静的メソッド)によるクラスローディングが特定されず、このときinstanceの初期化はlazy loadingの効果に達していないことは明らかであり、このインスタンスを使用しないとメモリが浪費されます.
3、怠け者モードアップグレード版(二重判空検査)
このモードは完璧に見え、synchronizedが呼び出されることが多い問題を解決し、本当にオブジェクトを使用していないときにメモリを無駄にすることはありません.これまでの単例モードの理解はここまでしかありませんでした.
4、静的内部クラス
この方法は餓漢モードのアップグレード版と言えるが,同様にクラスロードメカニズムを利用して安全を保証し,synchronizedの効率問題(上記の方法よりは向上は大きくないが,一定の向上がある)を回避し,lazy loadingの効果も保証した.
5、列挙
effective javaで強く推奨されている単例モードですが、残念ながら私はこの本を読んだときにこの部分を無視しました(当時は単例では新しいことは言えないと思いましたが)、マルチスレッド同期の問題を避けるだけでなく、逆シーケンス化や反射を防止して新しいオブジェクトを再作成することができ、唯一の欠陥はロードを遅らせることができない可能性があります.
ここは実際に読み始めたときに疑問に思ったことがありますが、資料や本をよく見てみると、自分の基礎知識点がしっかりしていないことに気づきました.1、なぜ列挙はマルチスレッド同期の問題を避けることができるのか.2、なぜ列挙は逆シーケンス化を防止して新しいオブジェクトを再作成できるのか.3、なぜ列挙は反射を防止して新しいオブジェクトを作成することができますか?4、逆シーケンス化列挙で新しいオブジェクトを作成したくない場合は、どうすればいいですか?
まとめ:1、列挙はJVMにおいて実際にjavaを継承するためである.lang.Enumは実装されているので、列挙自体がクラスであり、逆コンパイルにより、JVMでは列挙が抽象クラスになるとともに、静的コードブロックによってオブジェクトが割り当てられ、静的コードブロックは仮想マシンによってマルチスレッドの場合の同期問題が保証されるため、enumには同期問題がないことがわかります.
2、javaのシーケンス化実装では、実際にはObjectInputStreamとObjectOutputStreamによって行われ、javaはObjectInputStreamで列挙を個別に処理し、readObjectによって新しいオブジェクトを再取得するのではなく、EnumのvalueOfによってオブジェクトを取得し、valueOfがオブジェクトを変更しないのは、シーケンス化時にJavaが列挙オブジェクトのname属性を結果に出力するだけで、逆シーケンス化時にjavaを通過するためである.lang.EnumのvalueOfメソッドは、名前に基づいて列挙オブジェクトを検索します.同時に、コンパイラは、このようなシーケンス化メカニズムのカスタマイズを許可しないため、writeObject、readObject、readObject、readObject NoData、writeReplace、readResolveなどの方法を無効にします.
3、第一の原因と同様に、列挙自体は抽象クラスであり、抽象クラスはインスタンス化を許さないため、反射の安全を保証している.
4、readResolveの形式を加えることで、元のオブジェクトがreadObjectによって変更されないようにする.ソースコードは以下の通りである.
ObjectInputStreamでreadObjectを呼び出すと、現在のクラスにreadResolveがあるかどうかを判断し、ある場合はこのメソッドを呼び出し、反射によってオブジェクトを取得し、前にnewから出てきたオブジェクトを置き換えます.
スレッドセキュリティ、クラスロードメカニズム、列挙実装原理、シーケンス化、反射などの複数の知識点を含む単一例モード.学んだとしても、振り返る価値がある.
シングル・インスタンス・モードは、まずスレッドの安全を保証する必要があるため、すべてのスレッドが安全ではないシングル・インスタンス・モードは議論の範囲内ではありません.
1、怠け者モード
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
この書き方は確かにスレッドの安全を保証することができるが、効率は非常に低く、毎回synchronizedキーワード同期を使用する必要がある.しかし、実際には99%の場合は同期は必要ありません
2、餓漢モード
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
この方式はクラスローディングメカニズムに基づいてマルチスレッドの同期問題を回避するが、instanceはクラスローディング時にインスタンス化され、クラスローディングの原因は様々であるが、単例モードではgetInstanceメソッドを呼び出すことが多いが、他の方式(または他の静的メソッド)によるクラスローディングが特定されず、このときinstanceの初期化はlazy loadingの効果に達していないことは明らかであり、このインスタンスを使用しないとメモリが浪費されます.
3、怠け者モードアップグレード版(二重判空検査)
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
このモードは完璧に見え、synchronizedが呼び出されることが多い問題を解決し、本当にオブジェクトを使用していないときにメモリを無駄にすることはありません.これまでの単例モードの理解はここまでしかありませんでした.
4、静的内部クラス
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
この方法は餓漢モードのアップグレード版と言えるが,同様にクラスロードメカニズムを利用して安全を保証し,synchronizedの効率問題(上記の方法よりは向上は大きくないが,一定の向上がある)を回避し,lazy loadingの効果も保証した.
5、列挙
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
effective javaで強く推奨されている単例モードですが、残念ながら私はこの本を読んだときにこの部分を無視しました(当時は単例では新しいことは言えないと思いましたが)、マルチスレッド同期の問題を避けるだけでなく、逆シーケンス化や反射を防止して新しいオブジェクトを再作成することができ、唯一の欠陥はロードを遅らせることができない可能性があります.
ここは実際に読み始めたときに疑問に思ったことがありますが、資料や本をよく見てみると、自分の基礎知識点がしっかりしていないことに気づきました.1、なぜ列挙はマルチスレッド同期の問題を避けることができるのか.2、なぜ列挙は逆シーケンス化を防止して新しいオブジェクトを再作成できるのか.3、なぜ列挙は反射を防止して新しいオブジェクトを作成することができますか?4、逆シーケンス化列挙で新しいオブジェクトを作成したくない場合は、どうすればいいですか?
まとめ:1、列挙はJVMにおいて実際にjavaを継承するためである.lang.Enumは実装されているので、列挙自体がクラスであり、逆コンパイルにより、JVMでは列挙が抽象クラスになるとともに、静的コードブロックによってオブジェクトが割り当てられ、静的コードブロックは仮想マシンによってマルチスレッドの場合の同期問題が保証されるため、enumには同期問題がないことがわかります.
2、javaのシーケンス化実装では、実際にはObjectInputStreamとObjectOutputStreamによって行われ、javaはObjectInputStreamで列挙を個別に処理し、readObjectによって新しいオブジェクトを再取得するのではなく、EnumのvalueOfによってオブジェクトを取得し、valueOfがオブジェクトを変更しないのは、シーケンス化時にJavaが列挙オブジェクトのname属性を結果に出力するだけで、逆シーケンス化時にjavaを通過するためである.lang.EnumのvalueOfメソッドは、名前に基づいて列挙オブジェクトを検索します.同時に、コンパイラは、このようなシーケンス化メカニズムのカスタマイズを許可しないため、writeObject、readObject、readObject、readObject NoData、writeReplace、readResolveなどの方法を無効にします.
3、第一の原因と同様に、列挙自体は抽象クラスであり、抽象クラスはインスタンス化を許さないため、反射の安全を保証している.
4、readResolveの形式を加えることで、元のオブジェクトがreadObjectによって変更されないようにする.ソースコードは以下の通りである.
if (classDesc.hasMethodReadResolve()){
Method methodReadResolve = classDesc.getMethodReadResolve();
try {
result = methodReadResolve.invoke(result, (Object[]) null);
} catch (IllegalAccessException ignored) {
} catch (InvocationTargetException ite) {
Throwable target = ite.getTargetException();
if (target instanceof ObjectStreamException) {
throw (ObjectStreamException) target;
} else if (target instanceof Error) {
throw (Error) target;
} else {
throw (RuntimeException) target;
}
}
}
ObjectInputStreamでreadObjectを呼び出すと、現在のクラスにreadResolveがあるかどうかを判断し、ある場合はこのメソッドを呼び出し、反射によってオブジェクトを取得し、前にnewから出てきたオブジェクトを置き換えます.
スレッドセキュリティ、クラスロードメカニズム、列挙実装原理、シーケンス化、反射などの複数の知識点を含む単一例モード.学んだとしても、振り返る価値がある.