zoukankan      html  css  js  c++  java
  • 单例模式

    单例,就是整个程序有且仅有一个实例。该类负责创建自己的对象,同时确保只有一个对象被创建。它的出现是为了节省资源。在Spring中,每个Bean默认就是单例的。在Java,一般常用在工具类的实现或创建对象需要消耗资源。

    单例模式:

    • 饿汉式(线程安全,调用效率高,但是,不能延时加载)
    • 懒汉式(线程安全,调用效率不高,但是,可以延时加载)
    • 双重检测锁式(由于JVM底层内部模型的问题,偶尔会出问题,不建议使用)
    • 静态内部类模式(线程安全,调用效率高,可以延时加载)
    • 枚举式(线程安全,调用效率高,但是,不能延时加载。并且可以天然地防止反射和反序列化漏洞!)

    如何选用:

     ----单例对象 占用资源少 不需要 延时加载

    • 枚举式 优于 饿汉式

    ----单例对象 占用资源大 需要延时加载

    • 静态内部类 优于 懒汉式

    饿汉模式
    线程安全,比较常用,效率高,但容易产生垃圾,因为一开始就初始化,不能延时加载

     1 package top.bigking.singleton;
     2 
     3 /**
     4  * @Author ABKing
     5  * @Date 2020/1/31 下午5:50
     6  * 单例模式之 饿汉式
     7  **/
     8 public class SingletonDemo1 {
     9     private static SingletonDemo1 instance = new SingletonDemo1();//类初始化时,立即加载
    10     private SingletonDemo1(){};
    11     public static SingletonDemo1 getInstance(){
    12         return instance;
    13     }
    14 }

    懒汉模式
    线程不安全,延迟初始化,效率低偏低,严格意义上不是不是单例模式,可以延时加载

     1 package top.bigking.singleton;
     2 
     3 /**
     4  * @Author ABKing
     5  * @Date 2020/1/31 下午5:50
     6  * 单例模式之 懒汉式
     7  **/
     8 public class SingletonDemo2 {
     9     private static SingletonDemo2 instance;
    10     private SingletonDemo2(){};
    11     public static synchronized SingletonDemo2 getInstance(){
    12         if(instance == null){
    13             instance =  new SingletonDemo2();
    14         }
    15         return instance;
    16     }
    17 }

    双重检测锁

     1 package top.bigking.singleton;
     2 
     3 /**
     4  * @Author ABKing
     5  * @Date 2020/2/3 下午6:48
     6  * 单例模式之 双重检测锁
     7  **/
     8 public class SingletonDemo3 {
     9     private static SingletonDemo3 instance;
    10     private SingletonDemo3(){};
    11     public static SingletonDemo3 getInstance(){
    12         if(instance == null){
    13             SingletonDemo3 sc;
    14             synchronized (SingletonDemo3.class){
    15                 sc = instance;
    16             }
    17             if(sc == null){
    18                 synchronized (SingletonDemo3.class){
    19                     if(sc == null){
    20                         sc = new SingletonDemo3();
    21                     }
    22                 }
    23                 instance = sc;
    24             }
    25         }
    26         return instance;
    27     }
    28 }

    静态内部类

     1 package top.bigking.singleton;
     2 
     3 /**
     4  * @Author ABKing
     5  * @Date 2020/2/3 下午8:32
     6  * 单例模式 之 静态内部类
     7  **/
     8 public class SingletonDemo4 {
     9     //懒加载,而且类的加载是天然的线程安全的,当调用getInstance()方法时,才会加载 静态内部类
    10     private static class SingletonClassInstance {
    11         private static final SingletonDemo4 instance = new SingletonDemo4();
    12     }
    13     //直接调用,不需要synchronized同步等待,高效并发
    14     public static SingletonDemo4 getInstance(){
    15         return SingletonClassInstance.instance;
    16     }
    17     private SingletonDemo4(){};
    18 
    19 }

     枚举式

     1 package top.bigking.singleton;
     2 
     3 /**
     4  * @Author ABKing
     5  * @Date 2020/2/4 下午5:16
     6  **/
     7 public enum  SingletonDemo5 {
     8     //枚举元素,天然的 单例对象
     9     INSTANCE;
    10 
    11     //添加自己需要的一些操作
    12     public void SingletonDemo5Operation(){
    13 
    14     }
    15 }

    使用反射 破解单例模式

     1     @Test
     2     public void testReflection() throws Exception {
     3         Class<SingletonDemo1>  clazz = (Class<SingletonDemo1>) Class.forName("top.bigking.singleton.SingletonDemo1");
     4         Constructor<SingletonDemo1> constructor = clazz.getDeclaredConstructor(null);
     5         constructor.setAccessible(true);
     6         SingletonDemo1 instance1 = constructor.newInstance();
     7         SingletonDemo1 instance2 = constructor.newInstance();
     8         System.out.println(instance1);
     9         System.out.println(instance2);
    10     }

     运行结果:

     1 top.bigking.singleton.SingletonDemo1@579bb367

    2 top.bigking.singleton.SingletonDemo1@1de0aca6 

    可以看到,两个对象的内存地址不一致。我们成功破解了单例模式

    ********* getConstructor()和getDeclaredConstructor()区别:*********

    getDeclaredConstructor(Class<?>... parameterTypes) 
    这个方法会返回制定参数类型的所有构造器,包括public的和非public的,当然也包括private的。
    getDeclaredConstructors()的返回结果就没有参数类型的过滤了。

    再来看getConstructor(Class<?>... parameterTypes)
    这个方法返回的是上面那个方法返回结果的子集,只返回制定参数类型访问权限是public的构造器。
    getConstructors()的返回结果同样也没有参数类型的过滤。

    接下来,我们寻求防止被破解的单例模式的写法。

    在类的私有构造器中判断是否已经创建对象,如果在已经创建的情况下还调用构造器,则抛出异常

     1 package top.bigking.singleton;
     2 
     3 /**
     4  * @Author ABKing
     5  * @Date 2020/1/31 下午5:50
     6  * 单例模式之 饿汉式
     7  **/
     8 public class SingletonDemo1 {
     9     private static SingletonDemo1 instance = new SingletonDemo1();//类初始化时,立即加载
    10     private SingletonDemo1(){
    11         if(instance != null){
    12             throw new RuntimeException();
    13         }
    14     };
    15     public static SingletonDemo1 getInstance(){
    16         return instance;
    17     }
    18 }

     显然,通过上述方法,在私有构造器中进行判断,就能防止通过反射破坏单例模式

    可是!通过反序列化仍然可以破坏单例模式

     1     //利用反序列化 破解 单例模式
     2     @Test
     3     public void testUnSerialize() throws IOException, ClassNotFoundException {
     4         SingletonDemo1 instance = SingletonDemo1.getInstance();
     5         System.out.println(instance);
     6 
     7         //序列化
     8         FileOutputStream fos = new FileOutputStream("/home/king/a.txt");
     9         ObjectOutputStream oos = new ObjectOutputStream(fos);
    10         oos.writeObject(instance);
    11         oos.close();
    12         fos.close();
    13 
    14         //反序列化
    15         ObjectInputStream ois = new ObjectInputStream(new FileInputStream("/home/king/a.txt"));
    16         SingletonDemo1 s = (SingletonDemo1) ois.readObject();
    17         System.out.println(s);
    18     }

    运行结果如下

     1 top.bigking.singleton.SingletonDemo1@579bb367

    2 top.bigking.singleton.SingletonDemo1@1c655221 

    显然,内存地址不一致,即  破坏了单例模式!

    那么如何防止反序列化破坏单例模式呢?

    通过在类中添加readResolve()方法即可:

     1 package top.bigking.singleton;
     2 
     3 import java.io.Serializable;
     4 
     5 /**
     6  * @Author ABKing
     7  * @Date 2020/1/31 下午5:50
     8  * 单例模式之 饿汉式
     9  **/
    10 public class SingletonDemo1 implements Serializable {
    11     private static SingletonDemo1 instance = new SingletonDemo1();//类初始化时,立即加载
    12     private SingletonDemo1(){
    13         if(instance != null){
    14             throw new RuntimeException();
    15         }
    16     };
    17     public static SingletonDemo1 getInstance(){
    18         return instance;
    19     }
    20 
    21     //反序列化时,如果定义了readResolve()则直接返回此方法指定的对象。而不需要创建新的对象
    22     public Object readResolve() {
    23         return instance;
    24     }
    25 }
    反序列化时,如果定义了readResolve()则直接返回此方法指定的对象。而不需要创建新的对象

    运行结果如下:

     1 top.bigking.singleton.SingletonDemo1@579bb367

    2 top.bigking.singleton.SingletonDemo1@579bb367 

     显然,内存地址一致,是同一个对象,防止了反序列化破坏单例模式

    使用多线程测试各种单例模式的运行速度

     1     //多线程测试单例模式的运行速度
     2     @Test
     3     public void testSpeed() throws InterruptedException {
     4         long start = System.currentTimeMillis();
     5         int threadNum = 10;
     6         final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
     7         for (int i = 0; i < threadNum; i++) {
     8             new Thread(new Runnable() {
     9                 @Override
    10                 public void run() {
    11                     for (int j = 0; j < 1000000; j++) {
    12                         Object o = SingletonDemo2.getInstance();
    13                         //Object o = SingletonDemo5.INSTANCE;
    14                     }
    15                     countDownLatch.countDown();
    16                 }
    17             }).start();
    18         }
    19         countDownLatch.await();
    20         long end = System.currentTimeMillis();
    21         System.out.println("总共耗时:" + (end - start));
    22     }

    参考文献:

    https://www.bilibili.com/video/av46777702?p=288 尚学堂java300集

    https://blog.csdn.net/u012306714/article/details/64918737 如何通过反射来创建对象?getConstructor()和getDeclaredConstructor()区别?

    金麟岂是池中物,一遇风云便化龙!
  • 相关阅读:
    linux 环境下安装oracle11g方法及安装过程中遇上的问题解决方法
    Opencv Mat的操作
    Opencv 的数据结构
    Opencv 摄像头矫正
    LM算法
    Python 正则表达式
    find grep
    Socket 入门
    Python thread
    Javascript实现页面跳转的几种方式
  • 原文地址:https://www.cnblogs.com/ABKing/p/12227234.html
Copyright © 2011-2022 走看看