23种设计模式-不安全的单例模式

出自:https://blog.csdn.net/lw_power/article/details/53261388#饿汉式

为什么要有单例的类呢? 
有些对象的创建消耗时间和内存是非常大的,恰恰好这些对象在我们的应用中只需要使用 1 个,如果不能得到控制,会造成资源的浪费。 
说明:就想我们的办公室、家里那些很贵的电器,比如电冰箱、空调、打印机、热水器这一类电器,一般情况下,在一个小范围内我们只用使用 1 个。比如我们办公室吧, 1 台打印机就够我们几个人用了,没有必要买 2 台打印机。类似地,Java 中有这样的一些对象,例如线程池、数据库连接池,一个应用程序中,我们只需要有 1 个这样的大对象。

很多朋友们学习设计模式,最先是从单例设计模式开始的,很容易地,我们知道,单例设计模式有两种写法:懒汉式与饿汉式,分别如下。

饿汉式

public class Singleton {
    // 把"唯一的"对象保存在单例类的属性里
    private static Singleton instance = new Singleton();

    // 构造器私有化,不能在类的外部随意创建对象
    private Singleton(){}

    // 提供一个全局的访问点来获得这个"唯一"的对象
    public static Singleton getInstance(){
        return instance;
    }
}

说明:类加载的时候就创建对象。 
优点:简单清楚,代码“看起来”优雅,很安全。(这里的“安全”,后面会解释,为什么会很安全)。 
缺点:没有实现懒加载。 
看了上面的分析,有的朋友要问了,不懒加载的话,又有什么关系呢?就让应用程序启动的时候一起加载就好了呀。“安全”和“懒加载”,两害相较取其轻,我要“安全”,损失一些所谓“性能”,看起来饿汉式的单例模式写法可以说相对“完美”一些。

懒汉式

/**
 * Created by liwei on 16/11/23.
 * 单例设计模式懒汉式写法。
 */
public class Singleton {
    // 把"唯一的"对象保存在单例类的属性里
    private static Singleton instance = null;

    // 构造器私有化,不能在类的外部随意创建对象
    private Singleton(){}

    // 提供一个全局的访问点来获得这个"唯一"的对象
    // 请注意,这样的代码再多线程环境下是有问题的
    // 很可能 instance = new Singleton(); 会被执行多次
    // 我们可以模拟多线程环境来检验我们的猜想
    public static Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

测试类:

public class SingletonPatternDemo {
    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        System.out.println(s1 == s2);
    }
}

说明:这是单线程情况下,单例测试类(方法)的写法。请记住,我们的程序一般是在多线程情况下运行的,所以请不要用这样的测试类去检验我们的单例设计模式。应该使用下面的测试类(方法)。

验证在多线程环境下懒汉式单例写法的不“安全”之处

为了验证我们的结论,我们修改一下懒汉式单例设计模式的代码: 
懒汉式单例设计模式(添加了部分注释和跟踪流程的代码):

public class Singleton {
    // 为了记录这个 Singleton 类被实例化的次数
    // 声明一个 static 类型的计数器
    private static int count;
    // 把"唯一的"对象保存在单例类的属性里
    private static Singleton instance = null;

    // 构造器私有化,不能在类的外部随意创建对象
    private Singleton() {
        System.out.println("Singleton 私有的构造方法被实例化 " + (++count) + " 次。");
    }

    // 提供一个全局的访问点来获得这个"唯一"的对象
    // 请注意,这样的代码再多线程环境下是有问题的
    // 很可能 instance = new Singleton(); 会被执行多次
    // 我们可以模拟多线程环境来检验我们的猜想
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

模拟多线程环境下单例设计模式类获得实例的测试代码:

public class SingletonPatternDemo {
    public static void main(String[] args) {
        Runnable task = ()->{
            String threadName = Thread.currentThread().getName();
            Singleton s1 = Singleton.getInstance();
            System.out.println("线程 " + threadName + "\t => " + s1.hashCode());
        };
        // 模拟多线程环境下使用 Singleton 类获得对象
        for(int i=0;i<1000;i++){
            new Thread(task,"" + i).start();
        }
    }
}

运行结果如下(部分)

Singleton 私有的构造方法被实例化 1 次。
Singleton 私有的构造方法被实例化 9 次。
线程 7     => 674311410
线程 11    => 828929086
线程 10    => 674311410
Singleton 私有的构造方法被实例化 2 次。
线程 2     => 1596980710
Singleton 私有的构造方法被实例化 6 次。
Singleton 私有的构造方法被实例化 4 次。
Singleton 私有的构造方法被实例化 7 次。
Singleton 私有的构造方法被实例化 8 次。
Singleton 私有的构造方法被实例化 1 次。
Singleton 私有的构造方法被实例化 5 次。
线程 17    => 2117422881
Singleton 私有的构造方法被实例化 3 次。
线程 1     => 522589741
线程 3     => 2117422881
线程 16    => 1757887318
线程 0     => 1757887318
......

分析:在懒汉式单例设计模式中,getInstance() 方法由原来饿汉式的 1 行代码

return instance;

变成

if (instance == null) { //(1)
    instance = new Singleton(); 
}
return instance; 

很有可能的一种情况是:线程 1 运行到 (1)处的时候,线程 2 抢到的 CPU 的执行权,进入 getInstance() 方法,运行了 instance = new Singleton();,但线程 2 创建了对象这件事情,线程 1 根本不知道,等到线程 1 重新获得 CPU 执行权的时候,从 (1) 处继续执行,又运行了 instance = new Singleton(); 这行代码,这样,“多余的”对象就被创建出来了。

类似地,大家可以验证一下,饿汉式就没有“安全问题”,因为饿汉式单例设计模式在所有线程启动之前就创建好单例类的对象了。你完全可以像我这样“很不专业地”写一个饿汉式设计模式。

// 这个代码是有问题的,不要这样写
// 这个代码是有问题的,不要这样写
// 这个代码是有问题的,不要这样写
public class Singleton {
    private static int count;
    private static Singleton instance = new Singleton();

    private Singleton() {
        System.out.println("Singleton 私有的构造方法被实例化 " + (++count) + " 次。");
    }

    public static Singleton getInstance() {
        // 这里的 if 判断纯属多余
        if (instance == null) {
            System.out.println("------ 检验程序会不会走到这个 if 方法里面来 ------");
            instance = new Singleton();
        }
        return instance;
    }
}

猜你喜欢

转载自blog.csdn.net/kangkang_hacker/article/details/80631328