设计模式之单例模式

设计模式之单例模式


一、基础单例模式

     基础单例模式。不考虑多线程的情况下的标准单例模式。

     多线程的情况:假设有N个线程同时访问、A线程走完判断singleton为空而还没有New时、线程B也走完了判断、这时会产生多个Singleton实例

public class Singleton {//普通的单利模式实现
    private Singleton(){}//私有化构造器、防止外部调用new Singleton
    private static Singleton singleton;
    public static Singleton getInstance(){
        if(singleton==null){//判断是否存在实例
            singleton=new Singleton();
        }
        return singleton;
    }
    
}

二、改进版支持多线程单例模式

     基础单例模式线程不安全、那么给getInstance()方法增加一个Synchronized吧。

public class UnpreparedSingleton {//
    private UnpreparedSingleton(){}//私有化构造器、防止外部调用new Singleton
    private static UnpreparedSingleton unpreparedSingleton;
    public synchronized static UnpreparedSingleton getInstance(){//不好
        if(unpreparedSingleton==null){//判断是否存在实例
            unpreparedSingleton=new UnpreparedSingleton();
        }
        return unpreparedSingleton;
    }
}

    大神指出此做法十分愚蠢。因为所有线程均会等待上一个线程执行完getInstance()方法、造成不讲道理的等待。

三、真·改进版支持多线程单例模式

    此写法弥补了上一个版本的不足、直接上代码。

public class NormalSingleton {
    private NormalSingleton(){}//私有化构造器、防止外部调用new Singleton
    private static NormalSingleton normalSingleton;
    public static NormalSingleton getInstance(){
        if(normalSingleton==null){
            synchronized(NormalSingleton.class){
                if(normalSingleton==null){//判断是否存在实例
                    normalSingleton=new NormalSingleton();
                }
            }
        }
        return normalSingleton;
    }
}

     注:如果不加第二层非空判断会造成当A线程调用完毕返回实例、B线程进入同步块。如果不加判断则会直接执行new Singleton。导致多个实例的出现

    大神说一般情况下此方法是可以了但是还是有特殊情况、原话是:*&……&*…*&&*&%¥%¥%¥%¥¥%#¥%%¥#%¥#¥%

     这才是原话:经过刚才的分析,貌似上述双重加锁的示例看起来是没有问题了,但如果再进一步深入考虑的话,其实仍然是有问题的。

              如果我们深入到JVM中去探索上面这段代码,它就有可能(注意,只是有可能)是有问题的。

              因为虚拟机在执行创建实例的这一步操作的时候,其实是分了好几步去进行的,也就是说创建一个新的对象并非是原子性操作。在有些JVM中上述做法是没有问题的,但是有些情况下是会造成莫名的           错误。

              首先要明白在JVM创建新的对象时,主要要经过三步。

              1.分配内存

              2.初始化构造器

              3.将对象指向分配的内存的地址

              这种顺序在上述双重加锁的方式是没有问题的,因为这种情况下JVM是完成了整个对象的构造才将内存的地址交给了对象。但是如果2和3步骤是相反的(2和3可能是相反的是因为JVM会针对字节码进        行调优,而其中的一项调优便是调整指令的执行顺序),就会出现问题了。

              因为这时将会先将内存地址赋给对象,针对上述的双重加锁,就是说先将分配好的内存地址指给synchronizedSingleton,然后再进行初始化构造器,这时候后面的线程去请求getInstance方法时,会认        为synchronizedSingleton对象已经实例化了,直接返回一个引用。如果在初始化构造器之前,这个线程使用了synchronizedSingleton,就会产生莫名的错误。(看不懂吧 -_-!)

四、终极完整版

  据说这是比较标准的单例模式。

public class BestSingleton {
    private BestSingleton(){}//私有化构造器、防止外部new
    public static BestSingleton getInstance(){
        return BestSingletonSon.bestSingleton;
    }
    private static class BestSingletonSon{//内部类
        protected static BestSingleton bestSingleton=new BestSingleton();
    }
}

  JVM在加载类的时候、先创建了静态内部类和其中的变量然后执行外部getInstance()方法、在getInstance()方法中、BestSingletonSon.bestSingleton反回了已经生成的对象的引用而不会去运行New了 保证了单例。

             1.Singleton最多只有一个实例,在不考虑反射强行突破访问限制的情况下。

             2.保证了并发访问的情况下,不会发生由于并发而产生多个实例。

             3.保证了并发访问的情况下,不会由于初始化动作未完全完成而造成使用了尚未正确初始化的实例。

 

posted @ 2017-09-26 22:48  最喜欢蕙蕙了  阅读(185)  评论(0编辑  收藏  举报