23种设计模式(一)单例模式

定义html

单例模式最初的定义出现于《设计模式》(艾迪生维斯理, 1994):“保证一个类仅有一个实例,并提供一个访问它的全局访问点。”java

Java中单例模式定义:“一个类有且仅有一个实例,而且自行实例化向整个系统提供。”spring

主要解决:一个全局使用的类频繁地建立与销毁。设计模式

什么时候使用:当您想控制实例数目,节省系统资源的时候。安全

特色框架

A这些类只能有一个实例;jvm

B可以自动实例化;函数

C这个类对整个系统可见,即必须向整个系统提供这个实例。优化

优势.net

  1. 减小时间开销
  2. 减小空间内存开销

实例

  1. 系统经过spring框架注入的对象默认是单例的
  2. 配置文件在系统启动时去远端拉取,在后续使用时不须要再进行建立配置文件实例

实现方式

 单例模式的实现一般有两种方式:“饿汉式”和“懒汉式”。

单例模式是将将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的惟一实例只能经过提供的入口得到[例如getInstance()方法], 事实上,经过Java反射机制是可以实例化构造方法为private的类的,那基本上会使全部的Java单例实现失效,咱们不考虑java反射机制

懒汉式

懒汉式在类建立的时候不会去建立实例,在第一次调用时才会去建立,可是会有线程安全问题

饿汉式

 在类建立的时候建立出实例,这样只会有一个实例被建立,没有线程安全不安全的问题

http://5b0988e595225.cdn.sohucs.com/images/20171127/8476d360e3cf4d678d7f31010b6b5de5.jpeg

单例模式初版

public class Singleton {

    private Singleton() {} //私有构造函数      

    private static Singleton instance = null; //单例对象    

    // 静态工厂方法      
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

为何这样写呢?咱们来解释几个关键点:

1.要想让一个类只能构建一个对象,天然不能让它随便去作new操做,所以Signleton的构造方法是私有的。

2.instance是Singleton类的静态成员,也是咱们的单例对象。它的初始值能够写成Null,也能够写成new Singleton()。至于其中的区别后来会作解释。

3.getInstance是获取单例对象的方法。

若是单例初始值是null,还未构建,则构建单例对象并返回。这个写法属于单例模式当中的懒汉模式。

若是单例对象一开始就被new Singleton()主动构建,则再也不须要判空操做,这种写法属于饿汉模式

这两个名字很形象:饿汉主动找食物吃,懒汉躺在地上等着人喂。

为何说刚才的代码不是线程安全呢?

假设Singleton类刚刚被初始化,instance对象仍是空,这时候两个线程同时访问getInstance方法:

由于Instance是空,因此两个线程同时经过了条件判断,开始执行new操做:

这样一来,显然instance被构建了两次。让咱们对代码作一下修改:

单例模式第二版:

public class Singleton {
    private Singleton() {} //私有构造函数         

    private static Singleton instance = null;//单例对象

    //静态工厂方法          
    public static Singleton getInstance() {
        if (instance == null) { //双重检测机制
            synchronized (Singleton.class) {//同步锁                                         
                if (instance == null) { //双重检测机制   
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

为何这样写呢?咱们来解释几个关键点:

1.为了防止new Singleton被执行屡次,所以在new操做以前加上Synchronized 同步锁,锁住整个类(注意,这里不能使用对象锁)。

2.进入Synchronized 临界区之后,还要再作一次判空。由于当两个线程同时访问的时候,线程A构建完对象,线程B也已经经过了最初的判空验证,不作第二次判空的话,线程B仍是会再次构建instance对象。

3.若是已经存在该对象的实例,再次进行访问时不用再对对象进行加锁操做。

像这样两次判空的机制叫作双重检测机制

假设这样的场景,当两个线程一先一后访问getInstance方法的时候,当A线程正在构建对象,B线程刚刚进入方法:

这种状况表面看似没什么问题,要么Instance还没被线程A构建,线程B执行 if(instance == null)的时候获得false;要么Instance已经被线程A构建完成,线程B执行 if(instance == null)的时候获得true。

真的如此吗?答案是否认的。这里涉及到了JVM编译器的指令重排。

指令重排是什么意思呢?好比java中简单的一句 instance = new Singleton,会被编译器编译成以下JVM指令:

memory =allocate(); //1:分配对象的内存空间

ctorInstance(memory); //2:初始化对象

instance =memory; //3:设置instance指向刚分配的内存地址

可是这些指令顺序并不是一成不变,有可能会通过JVM和CPU的优化,指令重排成下面的顺序:

memory =allocate(); //1:分配对象的内存空间

instance =memory; //3:设置instance指向刚分配的内存地址

ctorInstance(memory); //2:初始化对象

当线程A执行完1,3,时,instance对象还未完成初始化,但已经再也不指向null。此时若是线程B抢占到CPU资源,执行 if(instance == null)的结果会是false,从而返回一个没有初始化完成的instance对象。以下图所示:

如何避免这一状况呢?咱们须要在instance对象前面增长一个修饰符volatile。

单例模式第三版:

public class Singleton {
    private Singleton() {
    } //私有构造函数          

    private volatile static Singleton instance = null; //单例对象      


    //静态工厂方法
    public static Singleton getInstance() {
        if (instance == null) { //双重检测机制        
            synchronized (Singleton.class) { //同步锁   
                if (instance == null) { //双重检测机制     
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

The volatile keyword indicates that a value may change between different accesses, it prevents an optimizing compiler from optimizing away subsequent reads or writes and thus incorrectly reusing a stale value or omitting writes.

通过volatile的修饰,当线程A执行instance = new Singleton的时候,JVM执行顺序是什么样?始终保证是下面的顺序:

memory =allocate(); //1:分配对象的内存空间

ctorInstance(memory); //2:初始化对象

instance =memory; //3:设置instance指向刚分配的内存地址

如此在线程B看来,instance对象的引用要么指向null,要么指向一个初始化完毕的Instance,而不会出现某个中间态,保证了安全。

1. volatile关键字不但能够防止指令重排,也能够保证线程访问的变量值是主内存中的最新值。有关volatile的详细原理,自行百科。

 

用静态内部类实现单例模式:

public class Singleton{
       private static class LazyHolder{
       private static final Singleton INSTANCE= newSingleton();
       }
       private Singleton(){}
       public static Singleton getInstance(){
              returnLazyHolder.INSTANCE;
       }
}

这里有几个须要注意的点:

1.从外部没法访问静态内部类LazyHolder,只有当调用Singleton.getInstance方法的时候,才能获得单例对象INSTANCE。

2.INSTANCE对象初始化的时机并非在单例类Singleton被加载的时候,而是在调用getInstance方法,使得静态内部类LazyHolder被加载的时候。所以这种实现方式是利用classloader的加载机制来实现懒加载,并保证构建单例的线程安全

总结:

单例模式是为了实现整个系统内部在运行过程当中只有一个实例,(饿汉 懒汉  线程安全  双重判空  volidate防止jvm指令重排)

转自: http://www.sohu.com/a/206960903_479559

单例模式其余的实现方式,能够参考:http://www.runoob.com/design-pattern/singleton-pattern.html

也能够经过http://blog.csdn.net/jason0539/article/details/23297037/等博文,加深本身对单例模式的理解

相关文章
相关标签/搜索