设计模式(五)单件模式
单件模式
在某些问题中,一些类可能只需要一个对象,比如:线程池、缓存、对话框、注册表的对象等等。如果制造出多个实例,就会导致程序的行为异常或者资源的使用浪费等。
全局变量
对于简单的程序,可以在程序的初始化中用生成一个静态的实例。随后需要程序员约定统一使用这一实例。
但是全局变量并没有确保只会存在一个实例。由于该类的构造器仍然是公共的,程序员编写的代码可能会不经意间调用到这个构造器,导致实例不唯一。而这种问题的安全性是无法保障的。
并且,使用大量的全局变量指向小对象会造成命名空间的污染。
单件模式
单件模式使用私有的构造器,用静态变量保存一个唯一的实例,并用一个公共的方法提供全局访问。
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;
}
}

浙公网安备 33010602011771号