单例模式 之 单例模式——懒汉模式

懒汉模式:和饿汉模式不同,懒汉模式并不会一开始声明对象,而是需要等到调用时再声明对象。他很懒,所以你叫“它”它才会动...

代码1:

/**
 * 懒汉模式
 */
public class LazybonesSingleton1 {
    //先声明
    private static LazybonesSingleton instance;
    /**
     * 禁止外部构建
     */
    private LazybonesSingleton(){}

    /**
     * 对外提供调用方法
     * @return
     */
    public static LazybonesSingleton getInstance() {
        if(instance==null){
            instance=new LazybonesSingleton();
        }

        return instance;
    }

    /**
     * 测试
     * @param args
     */
    public static void main(String[] args) {
        for(int i=0;i<20;i++){
            new Thread(()->{
                System.out.println(LazybonesSingleton.getInstance());
            }).start();
        }
    }


}

代码1 测试结果:发现 第一个线程运行时 和剩下线程运行 ,并不是一个单例。

这是因为多线程时,很有可能出现两个或多个线程同时执行。

也就是说他们同时运行到了 if(instance==null) 所以都创建了一个对象的实例

com.company.LazybonesSingleton@55477623
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457

Process finished with exit code 0

代码2:根据上述问题,我们进行了优化  getInstance 使用了 synchronized

/**
 * 懒汉模式
 */
public class LazybonesSingleton2 {
    //先声明
    private static LazybonesSingleton instance;
    /**
     * 禁止外部构建
     */
    private LazybonesSingleton(){}

    /**
     * 对外提供调用方法
     * @return
     */
    public static synchronized LazybonesSingleton getInstance() {
        if(instance==null){
            instance=new LazybonesSingleton();
        }

        return instance;
    }

    /**
     * 测试
     * @param args
     */
    public static void main(String[] args) {
        for(int i=0;i<20;i++){
            new Thread(()->{
                System.out.println(LazybonesSingleton.getInstance());
            }).start();
        }
    }


}

代码2 测试结果:运行结果为同一个用例,保证了安全性。

但是这里出现了一个问题 synchronized 会让 线程变成串行执行synchronized后,其他线程会在外进行等待,直到运行结束。再有下一个线程运行,剩下未执行的线程继续等待,同样直到该线程运行结束 )。

虽然保证了安全性,但是性能却很差。

com.company.LazybonesSingleton@3996d457。
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457
com.company.LazybonesSingleton@3996d457

Process finished with exit code 0

代码3:我们再进行优化,将 synchronized  放到 if(instance==null) 里面

这样其他线程就不用在执行 getInstance() 时进行等待了,性能也有所改进。

/**
 * 懒汉模式
 */
public class LazybonesSingleton3 {
    //先声明
    private static LazybonesSingleton instance;
    /**
     * 禁止外部构建
     */
    private LazybonesSingleton(){}

    /**
     * 对外提供调用方法
     * @return
     */
    public static  LazybonesSingleton getInstance() {
        if(instance==null){
            synchronized (LazybonesSingleton.class){
                instance=new LazybonesSingleton();
            }
        }

        return instance;
    }

    /**
     * 测试
     * @param args
     */
    public static void main(String[] args) {
        for(int i=0;i<20;i++){
            new Thread(()->{
                System.out.println(LazybonesSingleton.getInstance());
            }).start();
        }
    }


}

代码3 测试结果:虽然性能有所改进,但是依然存在安全问题,

原因在于:多线程运行过程中很有可能同时运行到 if(instance==null),虽然会在外面等待由另一个线程执行完再执行,所以也会存在创建多个实例的问题

com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@4f0c4707
com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561

Process finished with exit code 0

-----------------------------以下是我运行多次的结果-----------------------------------

com.company.LazybonesSingleton@4f0c4707
com.company.LazybonesSingleton@4f0c4707
com.company.LazybonesSingleton@4f0c4707
com.company.LazybonesSingleton@55477623
com.company.LazybonesSingleton@4f0c4707
com.company.LazybonesSingleton@4f0c4707
com.company.LazybonesSingleton@4f0c4707
com.company.LazybonesSingleton@4f0c4707
com.company.LazybonesSingleton@4f0c4707
com.company.LazybonesSingleton@4f0c4707
com.company.LazybonesSingleton@4f0c4707
com.company.LazybonesSingleton@4f0c4707
com.company.LazybonesSingleton@4f0c4707
com.company.LazybonesSingleton@4f0c4707
com.company.LazybonesSingleton@4f0c4707
com.company.LazybonesSingleton@4f0c4707
com.company.LazybonesSingleton@4f0c4707
com.company.LazybonesSingleton@4f0c4707
com.company.LazybonesSingleton@4f0c4707
com.company.LazybonesSingleton@4f0c4707

Process finished with exit code 0




代码4:在 synchronized 中 再判断一次是否为空,这样便能解决 代码3产生的问题。

此种方式 可以叫 双重检测(DCL),便能解决 懒加载的安全问题

/**
 * 懒汉模式
 */
public class LazybonesSingleton {
    //先声明
    private static LazybonesSingleton instance;
    /**
     * 禁止外部构建
     */
    private LazybonesSingleton(){}

    /**
     * 对外提供调用方法
     * @return
     */
    public static  LazybonesSingleton getInstance() {
        if(instance==null){
            synchronized (LazybonesSingleton.class){
                if(instance==null){
                    instance=new LazybonesSingleton();
                }
            }
        }

        return instance;
    }

    /**
     * 测试
     * @param args
     */
    public static void main(String[] args) {
        for(int i=0;i<20;i++){
            new Thread(()->{
                System.out.println(LazybonesSingleton.getInstance());
            }).start();
        }
    }


}

代码4 运行结果:

com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561
com.company.LazybonesSingleton@19281561

Process finished with exit code 0