Rumah  >  Artikel  >  Java  >  Mari analisa singleton corak reka bentuk java bersama-sama

Mari analisa singleton corak reka bentuk java bersama-sama

WBOY
WBOYke hadapan
2022-11-07 16:56:261004semak imbas

Artikel ini membawakan anda pengetahuan yang berkaitan tentang java Ia terutamanya memperkenalkan kandungan yang berkaitan tentang corak tunggal dalam corak reka bentuk adalah kelas objek tunggal dimulakan sekali. Saya harap ia akan membantu semua orang.

Mari analisa singleton corak reka bentuk java bersama-sama

Pembelajaran yang disyorkan: "tutorial video java" Kaedah terbaik.

Apa itu singleton? Sebagai prinsip asas,

kelas objek tunggal hanya akan dimulakan sekali
. Di Jawa, kita boleh mengatakan bahawa hanya satu contoh objek kelas wujud dalam JVM. Dalam Android, kita boleh mengatakan bahawa terdapat hanya satu contoh objek kelas ini semasa menjalankan program.

Langkah pelaksanaan mudah mod tunggal:

    Kaedah pembinaan adalah peribadi, memastikan objek tidak boleh dibuat dari luar melalui baru.
  • Menyediakan kaedah statik untuk mendapatkan kejadian kelas ini.
  • Buat objek kelas ini di dalam kelas dan kembalikannya melalui kaedah statik dalam langkah 2.
  • Ikuti langkah di atas untuk menulis corak tunggal yang anda rasa lebih ketat, dan kemudian lihat jika singleton yang anda tulis boleh memenuhi syarat berikut:

Adakah singleton anda dimuatkan atas permintaan?

    Adakah benang tunggal anda selamat?
  • Adakah pantulan dan siri ganas tunggal anda selamat? 涉及到并发三要素:原子性、可见性、有序性
  • 1. Gaya Cina Lapar

Kelebihan:
//JAVA实现public class SingleTon {    //第三步创建唯一实例
    private static SingleTon instance = new SingleTon();    
    //第一步构造方法私有
    private SingleTon() {
    }    
    //第二步暴露静态方法返回唯一实例
    public static SingleTon getInstance() {        return instance;
    } 
}//Kotlin实现object SingleTon
Reka bentuk yang ringkas, menyelesaikan masalah instantiasi berbilang benang.

Kelemahan:

Apabila mesin maya memuatkan kelas SingleTon, pembolehubah statik kelas akan diberikan nilai semasa fasa permulaan, iaitu apabila mesin maya memuatkan kelas (Kaedah getInstance mungkin tidak dipanggil pada masa ini) telah dipanggil untuk mencipta contoh objek Selepas itu, sama ada objek contoh digunakan atau tidak, ia akan menduduki ruang memori. 2. Lazy Man Style new SingleTon();

Kelebihan:
//JAVA实现public class SingleTon {    //创建唯一实例
    private static SingleTon instance = null;    
    private SingleTon() {
    }    
    public static SingleTon getInstance() {        //延迟初始化 在第一次调用 getInstance 的时候创建对象
        if (instance == null) {
            instance = new SingleTon();
        }        return instance;
    } 
}//Kotlin实现class SingleTon private constructor() {    companion object {        private var instance: SingleTon? = null
            get() {                if (field == null) {
                    field = SingleTon()
                }                return field
            }        fun get(): SingleTon{            return instance!!
        }
    }
}
Reka bentuknya juga agak mudah, tidak seperti Hungry Man Style Apabila Singleton ini dimuatkan, ia diubah suai oleh statik Pembolehubah statik akan dimulakan kepada null Ia tidak akan menduduki memori pada masa ini Sebaliknya, objek contoh akan dimulakan dan dibuat atas permintaan apabila kaedah getInstance dipanggil buat kali pertama.

Kelemahan:

Tiada masalah dalam persekitaran satu benang Dalam persekitaran berbilang benang, isu keselamatan benang akan berlaku. Apabila dua utas menjalankan pernyataan

instane == null pada masa yang sama dan kedua-duanya lulus, mereka masing-masing akan membuat instantiat objek, jadi ia bukan lagi satu.

如何解决懒汉式在多线程环境下的多实例问题?

Kelas dalaman statik
  • //JAVA实现public class SingleTon {    
        private static class InnerSingleton{        private static SingleTon singleTon  = new SingleTon();
        }    public SingleTon getInstance(){        return InnerSingleton.singleTon;
        }    
        private SingleTon() {
        }
    }//kotlin实现class SingleTon private constructor() {
        companion object {        val instance = InnerSingleton.instance
        }    private object InnerSingleton {        val instance = SingleTon()
        }
    }
    Kaedah penyegerakan langsung
  • Kelebihan:
    //JAVA实现public class SingleTon {    //创建唯一实例
        private static SingleTon instance = null;    
        private SingleTon() {
        }    
        public static synchronized SingleTon getInstance() {        if (instance == null) {
                instance = new SingleTon();
            }        return instance;
        } 
    }//Kotlin实现class SingleTon private constructor() {  companion object {      private var instance: SingleTon? = null
              get() {              if (field == null) {
                      field = SingleTon()
                  }              return field
              }      @Synchronized
          fun get(): SingleTon{          return instance!!
          }
      }
    }
    Hanya satu benang boleh membuat instantiate objek melalui penguncian, yang menyelesaikan masalah keselamatan benang.

    Keburukan:

    Untuk kaedah statik, kata kunci yang disegerakkan akan mengunci keseluruhan Kelas Setiap kali kaedah getInstance dipanggil, urutan akan disegerakkan, yang sangat tidak cekap objek instance dicipta, Tidak perlu terus menyegerak.

    Catatan:

    此处的synchronized保证了操作的原子性和内存可见性。

    Blok kod disegerakkan (double semak mod kunci DCL)
  • Kelebihan:
    //JAVA实现 public class SingleTon {    //创建唯一实例
        private static volatile SingleTon instance = null;    
        private SingleTon() {
        }    
        public static SingleTon getInstance() {        if (instance == null) {
                synchronized (SingleTon.class) {   
                    if (instance == null) {
                        instance = new SingleTon();
                    }
                }
            }        return instance;
        } 
    }//kotlin实现class SingleTon private constructor() {    companion object {        val instance: SingleTon by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
                SingleTon() 
            }
      }
    }
    或者class SingleTon private constructor() {    companion object {        @Volatile private var instance: SingleTon? = null
            fun getInstance() =
                  instance ?: synchronized(this) {
                      instance ?: SingleTon().also { instance = it }
                  }
      }
    }
    Menambah blok kod penyegerakan untuk menentukan sama ada objek contoh wujud Jika ia tidak wujud, buatnya Pada masa ini, masalah itu boleh diselesaikan sepenuhnya, kerana walaupun berbilang rangkaian diperlukan untuk mendapatkannya, Instance objek, tetapi hanya satu utas akan memasuki blok kod penyegerakan pada masa yang sama Selepas objek dibuat pada masa ini, walaupun utas lain memasuki blok kod penyegerakan sekali lagi, kerana objek contoh telah dibuat, mereka akan kembali secara langsung. boleh. Tetapi mengapa kita perlu menilai contoh itu kosong lagi dalam langkah sebelumnya blok kod penyegerakan? Ini kerana selepas kami mencipta objek instance, kami secara langsung menentukan sama ada objek instance itu kosong Jika ia tidak kosong, kembalikan terus, yang mengelakkan daripada memasuki blok kod penyegerakan semula dan meningkatkan prestasi.

    Kelemahan:

    Tidak dapat mengelakkan pantulan ganas untuk mencipta objek.

    Catatan:

    此处的volatile发挥了内存可见性及防止指令重排序作用。

    3 Penghitungan untuk melaksanakan tunggal

Penghitungan untuk melaksanakan tunggal Ini adalah. kaedah yang paling disyorkan, kerana ketunggalan tidak boleh dimusnahkan walaupun melalui siri, refleksi, dsb.

public enum SingletonEnum {    INSTANCE;    public static void main(String[] args) {        System.out.println(SingletonEnum.INSTANCE == SingletonEnum.INSTANCE);
    }
}

四、如何避免单例模式反射攻击

以最初的DCL为测试案例,看看如何进行反射攻击及又如何在一定程度上避免反射攻击。反射攻击代码如下:

 public static void main(String[] args) {

     SingleTon singleton1 = SingleTon.getInstance();
     SingleTon singleton2 = null;

     try {
         Class<SingleTon> clazz = SingleTon.class;
         Constructor<SingleTon> constructor = clazz.getDeclaredConstructor();
         constructor.setAccessible(true);
         singleton2 = constructor.newInstance();
     } catch (Exception e) {
         e.printStackTrace();
     }

     System.out.println("singleton1.hashCode():" + singleton1.hashCode());
     System.out.println("singleton2.hashCode():" + singleton2.hashCode());
 }

执行结果:

 singleton1.hashCode():1296064247
 singleton2.hashCode():1637070917

通过执行结果发现通过反射破坏了单例。 如何保证反射安全呢?只能以暴制暴,当已经存在实例的时候再去调用构造函数直接抛出异常,对构造函数做如下修改:

  public class SingleTon {     //创建唯一实例
     private static volatile SingleTon instance = null;   
     private SingleTon() {         if (instance != null) {             throw new RuntimeException("单例构造器禁止反射调用");
         }
     }   
     public static SingleTon getInstance() {         if (instance == null) {
           synchronized (SingleTon.class) {   
               if (instance == null) {
                   instance = new SingleTon();
               }
           }
       }       return instance;
     } 
 }

此时可防御反射攻击,抛出异常如下:

 java.lang.reflect.InvocationTargetException
 at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
 at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
 at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
 at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
 at com.imock.demo.TestUtil.testSingleInstance(TestUtil.java:45)
 at com.imock.demo.TestUtil.main(TestUtil.java:33)
 Caused by: java.lang.RuntimeException: 单例构造器禁止反射调用
 at com.imock.demo.SingleTon.<init>(SingleTon.java:16)
 ... 6 more Exception in thread "main" java.lang.NullPointerException
 at com.imock.demo.TestUtil.testSingleInstance(TestUtil.java:49)
 at com.imock.demo.TestUtil.main(TestUtil.java:33) 
 Process finished with exit code 1

然后我们把上述测试代码修改如下(调换了singleton1的初始化顺序)

 public static void main(String[] args) {
     SingleTon singleton2 = null;

     try {
         Class<SingleTon> clazz = SingleTon.class;
         Constructor<SingleTon> constructor = clazz.getDeclaredConstructor();
         constructor.setAccessible(true);
         singleton2 = constructor.newInstance();
     } catch (Exception e) {
         e.printStackTrace();
     }

     System.out.println("singleton2.hashCode():" + singleton2.hashCode());

     SingleTon singleton1 = SingleTon.getInstance(); //调换了位置,在反射之后执行
     System.out.println("singleton1.hashCode():" + singleton1.hashCode());
 }

执行结果:

 singleton2.hashCode():1296064247
 singleton1.hashCode():1637070917

发现此防御未起到作用。

缺点:

  • 如果反射攻击发生在正常调用getInstance之前,每次反射攻击都可以获取单例类的一个实例,因为即使私有构造器中使用了静态成员(instance) ,但单例对象并没有在类的初始化阶段被实例化,所以防御代码不生效,从而可以通过构造器的反射调用创建单例类的多个实例;
  • 如果反射攻击发生在正常调用之后,防御代码是可以生效的;

如何避免序列化攻击?只需要修改反序列化的逻辑就可以了,即重写 readResolve() 方法,使其返回统一实例。

   protected Object readResolve() {       return getInstance();
   }

脆弱不堪的单例模式经过重重考验,进化成了完全体,延迟加载,线程安全,反射及序列化安全。简易代码如下:

  • 饿汉模式

    public class SingleTon {    private static SingleTon instance = new SingleTon();    
        private SingleTon() {        if (instance != null) {              throw new RuntimeException("单例构造器禁止反射调用");
             }
        }    public static SingleTon getInstance() {        return instance;
        } 
    }
  • 静态内部类

    public class SingleTon {    
        private static class InnerStaticClass{        private static SingleTon singleTon  = new SingleTon();
        }    public SingleTon getInstance(){        return InnerStaticClass.singleTon;
        }    
        private SingleTon() {       if (InnerStaticClass.singleTon != null) {           throw new RuntimeException("单例构造器禁止反射调用");
           }
        }
    }
  • 懒汉模式

    public class SingleTon {    //创建唯一实例
        private static SingleTon instance = null;    
        private SingleTon() {        if (instance != null) {              throw new RuntimeException("单例构造器禁止反射调用");
            }
        }    
        public static SingleTon getInstance() {        //延迟初始化 在第一次调用 getInstance 的时候创建对象
            if (instance == null) {
                instance = new SingleTon();
            }        return instance;
        } 
    }

    缺点:

    • 如果反射攻击发生在正常调用getInstance之前,每次反射攻击都可以获取单例类的一个实例,因为即使私有构造器中使用了静态成员(instance) ,但单例对象并没有在类的初始化阶段被实例化,所以防御代码不生效,从而可以通过构造器的反射调用创建单例类的多个实例;
    • 如果反射攻击发生在正常调用之后,防御代码是可以生效的。

(枚举实现单例是最为推荐的一种方法,因为就算通过序列化,反射等也没办法破坏单例性,底层实现比如newInstance方法内部判断枚举抛异常)

推荐学习:《java视频教程

Atas ialah kandungan terperinci Mari analisa singleton corak reka bentuk java bersama-sama. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:juejin.im. Jika ada pelanggaran, sila hubungi admin@php.cn Padam