设计模式之单例模式

单例模式(singleton pattern)
顾明思议,单例模式就是在一个类使用过程中保证这个类只有一个实例。下面把实现了单例模式的类简称为“单例类”。
一、最简单的单例类
如果一个类是单例的,那么这个类的构造方法需要设置为私有private

public class SingletonPatternVersion01 {
    private static SingletonPatternVersion01 spv=new SingletonPatternVersion01();
    private SingletonPatternVersion01(){
        //empty
    }
    public static SingletonPatternVersion01 getInstance(){
        return SingletonPatternVersion01.spv;
    }
}

上面是最简单的单例类,这个类不存在多线程问题,同时使用过程中也不会出现错误。但是,这个类的唯一实例会在类加载之后就被创建了。如果这个类不那么“重”,当然这种方式创建的单例类没有任何问题,但是实际应用中单例类往往很“重”,所以在类加载之后就把该实例化是得不偿失的。
二、实现了“懒加载”机制的单例类
为了解决上面的这个问题,引入了“懒加载”机制,使得在真正开始使用这个唯一实例的时候才初始化这个类。

public class SingletonPatternVersion02 {
    private static SingletonPatternVersion02 spv;
    private SingletonPatternVersion02(){
        //empty
    }
    public static SingletonPatternVersion02 getInstance(){
        if(spv==null){
            spv=new SingletonPatternVersion02();
        }
        return spv;
    }

这样就单线程的情况就很棒了,但是在多线程的情况下就会出现问题。
多线程环境下问题剖析:假设现在有两个线程,线程A和线程B。线程A执行完spv==null之后让出CPU,线程B开始执行。线程B发现spv==null依然成立,所以new了一个实例之后正常返回了。然后线程A继续执行,同样new了一个新的实例返回了。这样就会导致出现两个不同的实例。
三、保证了线程安全和实现了懒加载的单例类
为了解决上面的问题,引入了同步机制

public class SingletonPatternVersion03 {
    private static SingletonPatternVersion03 spv;
    private SingletonPatternVersion03(){
        //empty
    }
    public static SingletonPatternVersion03 getInstance(){
        synchronized(SingletonPatternVersion03.class){
            if(spv==null)
                spv=new SingletonPatternVersion03();
        }
        return spv;
    }
}

这样问题就迎刃而解了。然而,如果这个程序在一个具有非常多的线程环境下执行,并且很多线程都需要获得该的唯一实例时,该程序的执行效率将会大大降低,这是因为加入同步化块之后,本来“并行”的线程变成了“串行”。
四、高效率、保证了线程安全和实现了懒加载的单例类
为了解决上面的问题,引入了双重检查(double check)机制

public class SingletonPatternVersion04 {
    private static  SingletonPatternVersion04 spv;
    private SingletonPatternVersion04(){
        //empty
    }
    public static SingletonPatternVersion04 getInstance(){
        if(spv==null)
        {
            synchronized (SingletonPatternVersion04.class){
                if(spv==null)
                    spv=new SingletonPatternVersion04();
            }
        }
        return spv;
    }
}

这样就可以保证在唯一实例被创建之后,其他线程不需要去竞争锁了。上面的单例类看似没有问题了,但实际上还有存在一个很“隐晦”的问题——空指针问题。
空指针问题产生解析:JVM在执行程序的时候,为了提高java程序的执行效率,会进行一些优化,“重排序”就是一种优化。“重排序”的意思就是在不影响执行结果的情况下,为了提高程序的执行效率改变了程序的执行顺序。而上面的程序开始new SingletonPatternVersion04()的之后,JVM会在堆中为该类的实例分配存储空间,但是JVM编译后的指令进行重排序之后,执行new的线程并不会等待类的实例完全初始化完成,就会继续执行从而把spv这实例的引用返回去了。这个时候如果使用spv对一些还没有初始化完成的字段进行操作就会导致空指针异常。
五、没有问题单例类
为了解决上面的问题,可以使用volatile,因为volatile会保证new该类的时候不会进行重排序,从而不会出现还没有完全创建实例就返回引用的情况。

public class SingletonPatternVersion05 {
    private volatile static SingletonPatternVersion05 spv;
    private SingletonPatternVersion05(){
        //emmpty
    }
    public static SingletonPatternVersion05 getInstance(){
        if(spv==null){
            synchronized(SingletonPatternVersion05.class){
                if(spv==null)
                    spv=new SingletonPatternVersion05();
            }
        }
        return spv;
    }
}

这种单例类的实现方式不会出现线程安全问题和空指针问题,同时具有懒加载机制,没有任何问题了。但是,还不过优雅。
六、优雅的单例类
需要知道:首先,一个类执行类加载的时候创建的实例不会进行“重排序”,也就是不会出现上面提到的“空指针”问题。再者,类的内部类不会随着该类的类加载而加载。

public class SingletonPatternVersion06 {
    private SingletonPatternVersion06(){
        //empty
    }
    private static class InstanceHolder{
        private final static SingletonPatternVersion06 spv=new SingletonPatternVersion06();
    }
    public static SingletonPatternVersion06 getInstance(){
        return InstanceHolder.spv;
    }
}

有上面可以看出,只有在调用getInstance()的时候,内部类才会进行类加载,所以实现了“懒加载”机制。同时该类的内部类进行类加载的时候创建了该类的唯一实例,所以不会出现空指针问题。同时,也不会出现线程安排问题。
七、单例模式提出者推荐的创建单例类的方式
如果对枚举的实现原理有一定了解的话就会知道,枚举实际上就是一个继承了Enum类的类。在使用枚举的时候,枚举类会进行类加载,然后为每一个枚举变量(每个枚举变量对应一个静态变量)创建一个该枚举类的实例,并且只创建一次。所以其实和“六”中机制是一样的,不同的是利用的枚举的机制而已。

public class SingletonPatternVersion07 {
    private SingletonPatternVersion07(){
        //empty
    }
    private enum InstanceHolder{
        INSTANCE;
        private final SingletonPatternVersion07 spv;
        InstanceHolder(){
            spv=new SingletonPatternVersion07();
        }
        public SingletonPatternVersion07 getInstance(){
            return INSTANCE.getInstance();
        }
    }
    public static SingletonPatternVersion07 getInstance(){
        return InstanceHolder.INSTANCE.getInstance();
    }
}

第五、六、七章用来创建实现了单例模式的类都是没有问题的,个人认为第六种比较棒。不过,因人而异吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值