深刻理解单例模式的几种实现方式

前言spring

单例模式是一种很经常使用的设计模式,其定义是单例对象的类只容许有一个实例存在。在使用spring自动建立对象时默认就是单例的。编程

使用场景设计模式

须要频繁的对对象进行建立与销毁,若是工具类对象安全

1、饿汉式(静态变量)多线程

public class Singleton1 {

    private static final Singleton1 INSTANCE = new Singleton1();
    //静态工厂
    public static Singleton1 getInstance(){
        return INSTANCE;
    }

}

饿汉式就是在类加载初始化的时候就建立了对象,尽管你还不须要使用他。相对来讲我是比较喜欢这种方式的,既没有线程安全,速度也快,固然这是以牺牲空间换取速度,你们能够根据实际状况,根据该对象被建立的几率酌情使用该方法。ide

2、饿汉式(静态代码块工具

public class Singleton2 {

    private static Singleton2 instance = null;
    //静态代码块
    static {
        instance = new Singleton2();
    }

    public Singleton2 getInstance(){
        return instance;
    }

}

静态代码块实现的饿汉式与上述第一种方式相似,都是在类初始化的时候就建立对象,一样是线程安全的,其实就是JVM帮咱们避免了线程安全,由于类只要加载一次,因此只会建立一个对象。性能

3、懒汉式(多线程下不可用)优化

public class Singleton3 {

    private static Singleton3 instance = null;

    public static Singleton3 getInstance(){
        if (null == instance){       //(1)             instance = new Singleton3();   //(2)
        }
        return instance;
    }

}

 这个懒汉式写法是线程不安全的,只能在单线程状况下使用。假设有两个线程A和B,线程A运行到(1)行代码时时间片已经完了,此时CPU切换执行线程B,线程B完成整个流程,也就是说这个时候instance已经不为空了,而后再执行线程A,此时线程A就会执行(2)行语句从新建立新的实例。spa

4、懒汉式(多线程下可用,但效率低)

public class Singleton4 {

    private static Singleton4 instance = null;

    public static synchronized Singleton4 getInstance() {
        if (null == instance){
            instance = new Singleton4();
        }
        return instance;
    }
}

第四个方法与第三个方法的区别在于在getInstance方法中加了synchronized同步锁,这样会致使只有等一个线程运行完后另外一个线程才能进入该方法,固然不会有线程安全的问题,但咱们只是在一开始实例化对象时须要,以后只须要直接返回对象便可,这样至关于每次都要去查询对象是否实例化,且还要排队查询,这样显然很影响性能,至关于每一个线程都要排队执行该方法。因此不推荐使用这种写法。

5、静态内部类

public class Singleton5 {

    private static class insideClass{
        private static final Singleton5 instance = new Singleton5();
    }

    public static Singleton5 getInstance(){
        return insideClass.instance;
    }

}

静态内部类与懒汉式相似,只不过是JVM帮咱们解决了多线程的问题。在调用getInstance()方法是会加载内部类insideClass,也就会实例化instance,ClassLoader类加载机制会帮咱们保证只有一个线程去初始化该内部类,也就只会生成一个实例。看起来这个方法又是懒加载,又是线程安全好像很完美的样子,但设计是要落地与业务的,没有最好的写法,只有最合适的写法。假设建立这个对象花费的时间很长,这无疑会给初次调用getInstance()方法的用户带来很差的体验,那么这种写法也是不可取的。

 6、双重检查

public class Singleton6 {

    private static volatile Singleton6 instance = null;   //(1) private Singleton6(){}

    public static Singleton6 getInstance(){
        if (null == instance){
            synchronized (Singleton6.class){
                if (null == instance){
                    instance = new Singleton6();
                }
            }
        }
        return instance;
    }
}

Double-Check在多线程编程中是很常见的。经过两次检查和静态代码块来限制同一时间只有个线程在实例化对象,进而控制只有一个实例产生。看起来貌似很完美,但你们有没有看到(1)行中的volatile关键字呢?他又有什么做用呢?

双重检查的写法在JDK1.5是有问题的,由于JVM优化会对指令进行重排序。咱们理想中建立一个对象的过程是:a->b->c

a.JVM分配内存空间

b.初始化对象

c.将instance指向分配的内存空间

但实际上执行的顺序多是 a->c->b

假设有线程A和线程B,线程A执行new Singleton6(),指令执行的顺序是a->c->b,此时只是将instance执行分配的内存空间,也就是说instance此时不等于null,这个时候线程B获取instance实例去执行操做,但实际上对应的内存空间尚未初始化,那么将会出现错误。

相关文章
相关标签/搜索