zoukankan      html  css  js  c++  java
  • 设计模式总纲——单例设计模式

      前两天写了设计模式总纲,今天就来讲讲我们在工程代码中最最最常用的设计模式了——单例设计模式,这个模式在工程代码上的出现率几乎为99.99999%,但是虽然很常用,但是用的好的人却不多,今天我们就来深入的说一说单例设计模式。

      在学习一项新的知识之前,我们都要向自己提出三个问题,为什么要用这个知识,这个知识用在哪里,这个知识怎么用?既 why,where,how,3W模式,我们先来谈谈为什么需要单例设计模式,先来想想,如果一个工具类,比如一个读取配置文件的工具类,这类工具只是特定的功能类,既读取指定的文件内容,这种类我们在使用的时候只需要建造一个就行了,然后在整个系统之中都用这个类来进行指定文件的读取即可,但是如果在设计之初没有考虑好,并没有把其设计成单例,导致整个系统中分布多个类似的功能类,一方面,导致了系统资源的浪费,如果该配置文件内容较小,对内存来说还好,但是如果是几百M或者几个G的配置文件的内容的话,就会造成系统资源的严重浪费,导致内存泄露,一方面也会让代码显得异常凌乱。

      为了解决这种问题,既对于只是解决单一功能的功能类,我们最好的做法就是将其设计成单例,接下来我们来看看我们要怎么来实现一个单例。

      正所谓万丈高楼平地起,再复杂的功能也是由一行行简单的代码组成的,那我们来看一下,要实现一个单例类的话,首先,肯定是不能让用户自行生产的,那就是说明不能让用户new,所以,就必须把构造函数设置成为私有的。

    1 public class Singleton {
    2     private Singleton(){}
    3 }

    好了,这就是单例了,哦,不,这应该是无例,因为把构造函数都弄成私有的了,什么都没有,用户拿到了这个类只能一脸懵逼,既然要变成单例,那肯定要给用户一个实例是吧,既然用户创建不了,那我们就给他一个,如下

     1 public class WorstLazySingleton {
     2     //1、私有化构造函数
     3     private WorstLazySingleton(){}
     4     
     5      //2、静态化一个实例,静态的实例保证了在每一个类中都是唯一的
     6     private static WorstLazySingleton instance = null;
     7     
     8     
     9     //3、返回该对象的实例,也必须是静态的方法,不然无法调用静态的实例
    10     public static WorstLazySingleton getInstance(){
    11         if(instance == null){
    12             instance = new WorstLazySingleton();
    13         }
    14         return instance;
    15     }
    16 }

    好了,一个新鲜的单例就出炉了,but,是不是有什么问题呢,为什么这个单例被加上了个Worst的标签,这个年代什么最惨,被人随意贴标签最惨,隔着屏幕都能感受到这个单例哀怨的眼神,但是,我们来看一看,这个单例,咋一看在单线程的环境下没问题,但是只要一到了多线程的环境下,妥妥的要出问题啊,随意写了个测试用例,跑了个10条线程来getInstance,竟然出现了4个不一样的hashCode,这个哪里是单例,明显是多的不能再多的”多例“了,好吧,这个worst的标签就先贴上去吧。那有同学就说了,我加同步方法啊,好,我们来为这个类加上同步方法,

      大致如下代码,

     1 public class BadLazySingleton {
     2     private static BadLazySingleton instance = null;
     3     
     4     private BadLazySingleton(){}
    5 //加上了synchronized的同步方法 6 public static synchronized BadLazySingleton getInstance(){ 7 if(instance == null){ 8 instance = new BadLazySingleton(); 9 } 10 return instance; 11 } 12 }

    这个方法现在被加上了synchronized了,运行一下多线程的测试环境,咋一看,好像没问题了,但是,我们再想一下下面的场景,如果在方法里面这个对象特别大的话,导致虚拟机调用的时间较长,或者在这个方法里面做了其他的 doSomething()方法的话,那其他线程只能乖乖的等待他的结束了,比如这个 方法执行时间用了10S,那10条线程过来,想想就有点小激动呢,一旦运行在服务器端上,那客户的等待时间,流失率是妥妥的,又有同学要提意见了,我们可以来缩小范围啊,我们不要再在方法上加同步了,好,那我们来看一看下个version的单例,

     1 public class NormalSingleton {
     2     //1、私有化构造方法
     3     private NormalSingleton(){}
     4     
     5     //2、静态化一个实例,静态的实例保证了在每一个类中都是唯一的
     6     private static NormalSingleton instance = null;
     7     
     8     public static NormalSingleton getInstance(){
     9         if(instance == null){
    10             //在局部进行同步,减少线程的等待时间
    11             synchronized (NormalSingleton.class) {
    12                 //进行双重判断,防止线程到了这一步刚好停住了,导致没有继续往下走而另外一条线程刚好进来
    13                 if(instance == null){
    14                     instance = new NormalSingleton();
    15                 }
    16             }
    17         }
    18         return instance;
    19     }
    20 }

    看来这个版本是比较Normal的Singleton了,不仅进行了同步,而且只需要进行一次同步,即只需要在第一次进行同步即可,还涉及到了双重判断,防止多线程上环境上的串线,这就是所谓的 Double-Check,but,有人就想到,为什么要我们要自己写同步,有的人表示已经累觉不爱了,不喜欢自己写同步了,要榨干JVM的最后一点资源,同步的重任就交给你了,(很用力的拍了拍虚拟机的肩膀),那我们来说一下,什么时候虚拟机会自己给自己加同步。

      1、在静态字段上或static{}块中的初始化数据时

      2、访问final字段时

      3、在创建线程之前创建对象时

      4、线程可以可以看见它将要处理的对象时

    那有了这四个条件,我们就可以想象,要让JVM自动来实现同步的话,就可以采用静态初始化器的方式,但是有人就会说了。静态初始化器虽然是同步的,但是类一加载的时候他就会去初始化这个对象了,哪怕我们不需要他也会去加载这些对象,那接下来来个脑经急转弯了,那如果我们可以让这个类在加载的时候不要去初始化这个对象不就可以喽?有人会说,有这等好事???

    还真有,这种类就叫做静态内部类,也叫作类级内部类,我们来看看代码:这种方法算是目前最好的方法之一了:(为什么叫之一....因为还有之二....)

     1 public class BestLazySingleton {
     2     //私有化构造方法
     3     private BestLazySingleton(){}
     4     
     5     //创建静态的内部类,让JVM自身来保证线程的安全性,而且该类只有在被调用到的时候才会去加载
     6     private static class SingletonHolder {
     7         private static BestLazySingleton instance = new BestLazySingleton();
     8     }
     9     
    10     public static BestLazySingleton getInstance(){
    11         return SingletonHolder.instance;
    12     }
    13 }

    这个类算是目前最好的懒加载的单例范本了,使用类级内部类,通过jvm来进行同步,当我们调用的时候才去初始化,进而实现了延迟加载的特性,而且也没有增加同步方法块,只增加了一个内部域的访问,成本相较前面的几种方法都非常低。

    最后我们来讲讲目前最好的单例的方法之二,这个方法是在《Effective Java》书中提到的,通过Enum来实现单例,首先我们需要了解一个大前提,Java中的Enum实质上也是一个功能齐全的类,也可以有自己的属性和方法,而且枚举算是单例的泛型化,本质上是单元素的枚举,而且也可以通过Enum来实现可变的多例类型的“单例”,具体代码如下

     1 public enum EnumSingleton {
     2     //定义一个枚举的元素,就代表了Singleton的一个实例
     3     instance;
     4     
     5     private String singletonData;
     6     
     7     public String getEnumSingleton(){
     8         return singletonData;
     9     }
    10     
    11     public void setEnumSingleton(String singletonData){
    12         this.singletonData = singletonData;
    13     }
    14 }

    也可以类似的写上 instance2,instance3.......对于Enum来说,都是单例,这种实现形式基于JDK1.5以及JDK1.5以上

    最后假设你不想使用懒加载的单例模型,你实在表示很想偷懒,那就使用饿汉式的单例吧,这种方法简单粗暴,并且是线程安全的,就是类一旦被加载的时候就会去实例化该对象,哪怕不使用该类的时候,具体代码如下:

     1 public class EagerSingleton {
     2     //直接实例化类实例,其他别无二致
     3     private static EagerSingleton instance = new EagerSingleton();
     4     
     5     private EagerSingleton(){}
     6     
     7     public static EagerSingleton getInstance() {
     8         return instance;
     9     }
    10 }

    这种方法简单粗暴,老少咸宜,但是性能如何就见仁见智了,

    好了,差不多晚上的JAVA单例设计模式就讲到这里了,最后贴上思维导图一张,就在总纲的基础上在Singletong的设计模式上添加的,下回我们再见,下回我们具体会讲到下一个CreationPattern中的Factory Method,敬请期待。

      如需转载请告知,转载请注明出处。

  • 相关阅读:
    Oracle主库存在Online Patch,备库该如何打上该补丁
    Oracle中如何构造一条在去年运行不报错今年运行报错的SQL语句
    Linux双网卡绑定启动网卡报错Error: Connection activation failed: Master connection not found or invalid
    Oracle备库GV$ARCHIVED_LOG.APPLIED的最新归档日志状态为"IN-MEMORY"(已经应用成功)对应主库的状态为"NO"
    Oracle关于ARCHIVELOG DELETION POLICY的配置解释以及RMAN-08137/RMAN-08591的原因探究
    Oracle Logminer的测试使用
    Oracle间隔分区(interval分区)的分区字段无法为NULL值
    Oracle绑定变量类型为timestamp导致V$SQL_BIND_CAPTURE不显示值
    即时性能分析工具 Pyroscope
    Go之Zap日志库集成Gin
  • 原文地址:https://www.cnblogs.com/algorithm-cpp/p/5507150.html
Copyright © 2011-2022 走看看