设计模式总纲——单例设计模式

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

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

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

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

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

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

 1 public class WorstLazySingleton {
 2     //一、私有化构造函数
 3     private WorstLazySingleton(){}
 4     
 5      //二、静态化一个实例,静态的实例保证了在每个类中都是惟一的
 6     private static WorstLazySingleton instance = null;
 7     
 8     
 9     //三、返回该对象的实例,也必须是静态的方法,否则没法调用静态的实例
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     //一、私有化构造方法
 3     private NormalSingleton(){}
 4     
 5     //二、静态化一个实例,静态的实例保证了在每个类中都是惟一的
 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的最后一点资源,同步的重任就交给你了,(很用力的拍了拍虚拟机的肩膀),那咱们来讲一下,何时虚拟机会本身给本身加同步。学习

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

  二、访问final字段时

  三、在建立线程以前建立对象时

  四、线程能够能够看见它将要处理的对象时

那有了这四个条件,咱们就能够想象,要让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,敬请期待。

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

相关文章
相关标签/搜索