概念
シングルトンパターン(シングルトンパターン)は、Javaで最も単純なデザインパターンの1つです。このタイプのデザインパターンは作成パターンであり、オブジェクトを作成するための最良の方法を提供します。
このパターンには、単一のオブジェクトのみが作成されるようにしながら、独自のオブジェクトの作成を担当する単一のクラスが含まれます。このクラスは、このクラスのオブジェクトをインスタンス化せずに直接アクセスできる一意のオブジェクトにアクセスする方法を提供します。
一般的な執筆
空腹のシングルトン
シングルトンクラスが最初にロードされたときにシングルトンを作成する
public class HungerSingleton{
private static final HungerSingleton instance =new HungerSingleton();
private HungerSingleton(){
}
public static HungerSingleton getInstance(){
return instance;
}
}
不利益
- メモリスペースの浪費。多数のシングルトンオブジェクトが作成されるシナリオには適していません。
怠惰なシングルトン
空腹のシングルトンによって引き起こされる可能性のあるメモリの浪費の問題を解決するために、怠惰なシングルトンが提案されています。レイジーシングルトンの特徴は、シングルトンオブジェクトが使用された場合にのみ初期化されることです。
安全でないスレッド
public class LazySingleton{
private static LazySingleton instance =null;
private LazySingleton(){
}
public static LazySingleton getInstance(){
if(instance==null){
return new LazySingleton();
}
return instance;
}
}
不利益
- 安全でないスレッド
スレッドセーフ
public class LazySingleton{
private static LazySingleton instance =null;
private LazySingleton(){
}
public static synchronized LazySingleton getInstance(){
if(instance==null){
return new LazySingleton();
}
return instance;
}
}
不利益
- シングルトンを確保するには同期してロックする必要がありますが、ロックすると効率に影響します。
ロックを再確認する
スレッドの安全性を解決するだけでなく、プログラムのパフォーマンスを向上させることができるため、ダブルチェックロックが提案されています
class SynchronizedSingleton{
private static volatile SynchronizedSingleton instance =null;
private SynchronizedSingleton(){
}
private static SynchronizedSingleton getInstance(){
if (instance==null){
synchronized (SynchronizedSingleton.class){
if (instance==null){
return new SynchronizedSingleton();
}
}
}
return instance;
}
}
不利益
- 結局のところ、同期ロック操作は引き続き使用され、パフォーマンスに影響します
内部クラスの実装
内部クラスのロードメソッドを利用します。外部クラスが初期化されるときに内部クラスはロードされず、内部クラスのメソッドが呼び出されたときにのみ内部クラスが初期化されます。
最高のパフォーマンスとスレッドの安全性を備えた怠惰なシングルトンです
class LazyInnerClassSingleton{
private LazyInnerClassSingleton(){
}
public static final LazyInnerClassSingleton getInstance() {
return LazyHoder.instance;
}
//延迟加载
private static class LazyHoder{
private static final LazyInnerClassSingleton instance=new LazyInnerClassSingleton();
}
}
反射破壊シングルトン
しかし、上記の方法は完璧ですか?最初にテスト例を見てみましょう
class LazyInnerClassSingletonTest{
public static void main(String[] args) {
try {
Class<?>clazz=LazyInnerClassSingleton.class;
Constructor c=clazz.getDeclaredConstructor(null);
c.setAccessible(true);//强吻
Object o1=c.newInstance();
Object o2=LazyInnerClassSingleton.getInstance();
System.out.println(o1==o2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
操作結果:
リフレクションを介して構築メソッドを呼び出し、getInstanceメソッドを呼び出すと、2つの異なるインスタンスが表示されたことがわかります。この状況を解決するにはどうすればよいですか。
class LazyInnerClassSingleton{
private LazyInnerClassSingleton(){
if(LazyHoder.instance!=null){
throw new RuntimeException("不允许创建多个实例");
}
}
public static final LazyInnerClassSingleton getInstance() {
return LazyHoder.instance;
}
//延迟加载
private static class LazyHoder{
private static final LazyInnerClassSingleton instance=new LazyInnerClassSingleton();
}
}
構築メソッドの判断と例外により、呼び出し元はgetInstanceを介してのみインスタンスを取得できます
シリアル化はシングルトンを破壊します
オブジェクトが作成されたら、オブジェクトをシリアル化してディスクに書き込み、次に使用するときにディスクからオブジェクトを読み取り、逆シリアル化してメモリオブジェクトに変換する必要がある場合があります。デシリアライズ後のオブジェクトは、メモリを再割り当てします。つまり、メモリを再作成します。シリアル化されたオブジェクトがシングルトンオブジェクトの場合、シングルトンを破棄します
public class HungerSingleton implements Serializable{
private static final HungerSingleton instance =new HungerSingleton();
private HungerSingleton(){
}
public static HungerSingleton getInstance(){
return instance;
}
public static HungerSingleton deepClone(HungerSingleton hs){
HungerSingleton i =null;
try {
//序列化对象
FileOutputStream fo=new FileOutputStream("HungerSingleton.obj");
ObjectOutputStream os=new ObjectOutputStream(fo);
os.writeObject(hs);
os.flush();
os.close();
//反序列化对象
FileInputStream fi=new FileInputStream("HungerSingleton.obj");
ObjectInputStream ois=new ObjectInputStream(fi);
i=(HungerSingleton)ois.readObject();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
return i;
}
public static void main(String[] args) {
HungerSingleton i=HungerSingleton.getInstance();
HungerSingleton ii=deepClone(i);
System.out.println(i==ii);
}
}
操作結果:
明らかに、シリアル化されたオブジェクトは手動で作成されたオブジェクトと矛盾しており、2回インスタンス化されました。では、この問題をどのように解決するのでしょうか?簡単です、追加するだけですreadResolve方法は大丈夫です
public class HungerSingleton implements Serializable{
private static final HungerSingleton instance =new HungerSingleton();
private HungerSingleton(){
}
public static HungerSingleton getInstance(){
return instance;
}
//序列化破坏单例的解决方案:
//重写readResolve方法
private Object readResolve(){
return instance;
}
}
結果をもう一度見てください。
登録シングルトン
登録されたシングルトンは、登録されたシングルトンとも呼ばれます。これは、各インスタンスが特定の場所に登録され、シングルトンが一意の識別子で取得されることを意味します。
列挙されたシングルトン
enum EnumSingleton{
INSTANCE;
private Object obj;
private Object getObj(){
return obj;
}
public static EnumSingleton getInstance(){
return INSTANCE;
}
public static void main(String[] args) {
EnumSingleton e1=EnumSingleton.getInstance();
EnumSingleton e2=EnumSingleton.getInstance();
System.out.println(e1==e2);
}
}
列挙シングルトンモードは、「EffectiveJava」という本で推奨されているシングルトンモードです。
不利益
- クラスが読み込まれると、すべてのオブジェクトがメモリ内で初期化されます。空腹のスタイルと同様に、多数のシングルトンオブジェクトが作成されるシナリオには適していません。
ThreadLocalシングルトン
以前、ThreadLocalについても説明しました。これは、単一のスレッドに固有であり、本質的にスレッドセーフです。
class ThreadLocalSingleton{
private ThreadLocalSingleton(){
}
private static final ThreadLocal<ThreadLocalSingleton> instance=new ThreadLocal<ThreadLocalSingleton>(){
@Override
protected ThreadLocalSingleton initialValue() {
return new ThreadLocalSingleton();
}
};
private static ThreadLocalSingleton getInstance(){
return instance.get();
}
}
ThreadLocalシングルトンは特別なシングルトンモードと見なすことができます。ThreadLocalの原理に興味がある場合は、前の記事を読んでThreadLocalを深く理解することができます。
総括する
空腹の中国のシングルトンを使用することをお勧めします。レイジーローディング効果が明確に達成される場合にのみ、内部クラスによって実装されたシングルトンパターンが使用されます。オブジェクトを作成するために逆シリアル化が必要な場合は、最初の列挙方法を使用してみてください。他に特別なニーズがある場合は、ダブルチェックロック方式の使用を検討できます。