网上很多关于单例模式写法的文章,不外乎饿汉和懒汉两种形式的讨论。很多人喜欢用懒汉式,因为觉得它实现了延迟加载,可以让系统的性能更好。但事实果真如此吗?我对此存疑。
首先我们检查一下饿汉和懒汉单例模式最简单的写法(这里不讨论哪种懒汉写法更好):
// 饿汉
public final class HungrySingleton {
private static final HungrySingleton INSTANCE = new HungrySingleton();
private HungrySingleton() {
System.out.println("Initializing...");
}
public static HungrySingleton getInstance() {
return INSTANCE;
}
}
// 懒汉
public final class LazySingleton {
private static LazySingleton INSTANCE;
private LazySingleton() {
System.out.println("Initializing...");
}
public static synchronized LazySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new LazySingleton();
}
return INSTANCE;
}
}
从理论上来说,HungrySingleton
的单例在该类第一次使用的时候创建,而 LazySingleton
的单例则在其 getInstance()
方法被调用的时候创建。至于网上有人声称“饿汉式不管用不用都会初始化”,纯属走路的时候步子迈得太大。谁的加载更迟?如果你只是调用它们的 getInstance()
方法来得到单例对象,则它们都是延迟加载,这样懒汉式没有任何意义,而且由于 LazySingleton
采取了同步措施,性能更低(可以说任何懒汉式的性能都低于饿汉式)。当你使用一个单例类的时候,难道第一步不是调用 getInstance()
么?所以在自己的代码里,我更喜欢用饿汉式。
下面用一个例子来测试加载顺序:
// 饿汉
System.out.println("Before");
HungrySingleton.getInstance();
System.out.println("After");
// 懒汉
System.out.println("Before");
LazySingleton.getInstance();
System.out.println("After");
输出结果都是:
Before
Initializing...
After
那么,懒汉模式还有什么存在意义?如果系统使用了某些需要在启动时对类进行扫描的框架,使用饿汉式的话,启动时间比懒汉式更长,如果使用了大量单例类,不利于开发阶段。在系统的正式运行阶段,所有的单例类迟早都要加载的,总的说来两者性能持平,但是懒汉式每次都至少多一个判断,所以越到后期越体现饿汉的优越性。
最后,推荐下《Effective Java》第二版指出的用枚举类型实现的饿汉单例模式:
// 饿汉
public enum HungrySingleton {
INSTANCE;
private HungrySingleton() {
}
}
这种写法不但最简洁,还能轻易扩展为实例数量固定的“多例模式”。