一、单例模式的要点:
- 单例类只能有一个实例
- 单例类必须自行创建自己的唯一的实例
- 单例类必须自行向其他对象提供这一实例
单例类的一个最重要的特点是类的构造函数是私有的,因此单例类不能被继承。
二、三种单例类
饿汉式单例类:
类被加载时,已被初始化。
public class EagerSingleton {
private static final EagerSingleton m_instance = new EagerSingleton();
private EagerSingleton() {}
public static EagerSingleton getInstance() {
return m_instance;
}
}
懒汉式单例类:
类被加载时不会实例化,第一次被引用时才实例化。
public class LazySingleton {
private static LazySingleton m_instance = null;
private LazySingleton() {}
synchronized public static LazySingleton getInstance() {
if (m_instance == null) {
m_instance = new LazySingleton();
}
return m_instance;
}
}
登记式单例类:
克服单例类不可继承的缺点,其子类的实例化方式只能是懒汉式。
注意:文中说明将登记式单例类的内部实例化方式从懒汉式改为饿汉式。
public class RegSingleton {
static private HashMap m_registry = new HashMap();
static {
RegSingleton x = new RegSingleton(); // 饿汉式
m_registry.put(x.getClass().getName(), x);
}
protected RegSingleton() {}
static public RegSingleton getInstance(String name) {
if (name == null) {
name = "com.javapatterns.singleton.demos.RegSingleton";
}
if (m_registry.get(name) == null) {
try {
m_registry.put(name, Class.forName(name).newInstance());
} catch (Exception e) {
System.out.println("Error happened.");
}
}
return (RegSingleton) (m_registry.get(name));
}
public String about() {
return "Hellow, I am RegSingleton";
}
}
子类(需要父类的帮助才能实例化):
public class RegSingletonChild extends RegSingleton {
public RegSingletonChild() {}
static public RegSingletonChild getInstance() {
return (RegSingletonChild) RegSingleton.getInstance("com.javapatterns.singleton.demos.RegSingletonChild");
}
public String about() {
return "Hellow, I am RegSingletonChild.";
}
}
缺点:
- 子类产生的实例不在父类的登记中
- 父类的实例必须存在才可能有子类的实例
三、单例类的状态
有状态的单例类:
其单例对象一般是可变单例对象,常当作状态库使用。
没有状态的单例类:
不变单例类,提供工具性函数的对象。
为什么需要使用单例模式:
类的实例化对象在很多地方使用,但是创建的地方在哪里不是很清楚,就需要类自己创建自己的实例,并向系统全程提供这一实例。
不完全的单例类:
构造函数公开化,外界可以直接实例化,违背单例类只有一个实例的特性,产生的原因:
- 初学者的错误。
- 当初考虑不周,将一个类设计成单例类,后来发现此类应当有多于一个的实例。可以考虑修改为多例类。
- 故意“改良”的单例模式。
在分散式的Java系统中使用单例模式时,尽量不要使用有状态的。
四、双重检查成例:
为了避免多线程环境中的晚实例化所产生的错误设计,将以下代码与懒汉式对比:
public class LazySingleton {
private static LazySingleton m_instance = null;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (m_instance == null) {
// More than one threads might be here!!!
synchronized(LazySingleton.class) {
if (m_instance == null) {
m_instance = new LazySingleton();
}
}
}
return m_instance;
}
}
双重检查成例对Java语言编译器不成立:
在Java编译器中,LazySingleton类的初始化m_instance变量赋值的顺序不可预料。如果一个线程在没有同步化的条件下读取m_instance引用,并调用这个对象的方法的话,可能会发现对象的初始化过程尚未完成,从而造成崩溃。
总结:
在一般情况下,使用饿汉式单例模式或者对整个静态工厂方法同步化的懒汉式单例模式足以解决在实际设计工作中遇到的问题。