设计模式(五)单件模式

单件模式

在某些问题中,一些类可能只需要一个对象,比如:线程池、缓存、对话框、注册表的对象等等。如果制造出多个实例,就会导致程序的行为异常或者资源的使用浪费等。

全局变量

对于简单的程序,可以在程序的初始化中用生成一个静态的实例。随后需要程序员约定统一使用这一实例。

但是全局变量并没有确保只会存在一个实例。由于该类的构造器仍然是公共的,程序员编写的代码可能会不经意间调用到这个构造器,导致实例不唯一。而这种问题的安全性是无法保障的。

并且,使用大量的全局变量指向小对象会造成命名空间的污染。

单件模式

单件模式使用私有的构造器,用静态变量保存一个唯一的实例,并用一个公共的方法提供全局访问。

package singleton;

public class Singleton {
    // 静态实例
    private static Singleton uniqueInstance;

    // 私有构造器
    private Singleton() {}
    // 全局访问接口
    public static Singleton getInstance() {
        if (uniqueInstance == null)
            uniqueInstance = new Singleton();
        return uniqueInstance;
    }
}

但是如果遇到多线程的问题,这种简单的单件模式可能无法维护实例的唯一性。

package singleton;

public class ChocolateBoiler {
    private boolean empty;
    private boolean boiled;

    private static ChocolateBoiler uniqueInstance;

    private ChocolateBoiler() {
        empty = true;
        boiled = false;
    }

    public static ChocolateBoiler getInstance() {
        if (uniqueInstance == null)
            uniqueInstance = new ChocolateBoiler();
        return uniqueInstance;
    }

    public void fill() {
        if (isEmpty()) {
            empty = false;
            boiled = false;
        }
    }

    public void drain() {
        if (!isEmpty() && isBoiled())
            empty = true;
    }

    public void boil() {
        if (!isEmpty() && !isBoiled())
            boiled = true;
    }

    public boolean isEmpty() {
        return empty;
    }

    public boolean isBoiled() {
        return boiled;
    }
}

上述代码使用单件模式实现一个巧克力工厂。当多个线程同时从全局访问处请求唯一的实例时,在初始化时可能出现问题:

线程1判断实例不存在 -> 线程2判断实例不存在 -> 线程1初始化实例 -> 线程2再次初始化实例

这是一个典型的多线程问题,简单的解决方案是用synchronized修饰全局访问方法。但由于线程冲突只会发生在实例未被创建的情况下,因此同步在后续的所有操作中都可能极大地降低程序的运行效率。

如果无法承担同步造成的效率降低,可以不使用延迟实例化的做法,在类被加载时就创建唯一的实例。

private static Singleton uniqueInstance = new Singleton();

但急切的创建可能会造成资源的浪费,如果还要关注资源,同时又要同步,可以利用双重检查加锁来减少同步的使用,又能确保实例化唯一。

package singleton;

public class Singleton {
    // volatile使变量在被更新时确保同步
    private volatile static Singleton uniqueInstance;

    private Singleton() {}
    public static Singleton getInstance() {	
        // 双重检查加锁
        if (uniqueInstance == null)
            synchronized (Singleton.class) { // 只在初始化实例时使用同步
         		if (uniqueInstance == null)
                    uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}
posted @ 2020-05-06 10:19  Aries99C  阅读(159)  评论(0)    收藏  举报