神奇好望角 The Magical Cape of Good Hope

庸人不必自扰,智者何需千虑?
posts - 26, comments - 50, trackbacks - 0, articles - 11
  BlogJava :: 首页 ::  :: 联系 :: 聚合  :: 管理

单例模式的一个疑问

Posted on 2012-01-10 17:39 蜀山兆孨龘 阅读(1842) 评论(6)  编辑  收藏 所属分类: Java SE

网上很多关于单例模式写法的文章,不外乎饿汉和懒汉两种形式的讨论。很多人喜欢用懒汉式,因为觉得它实现了延迟加载,可以让系统的性能更好。但事实果真如此吗?我对此存疑。

首先我们检查一下饿汉和懒汉单例模式最简单的写法(这里不讨论哪种懒汉写法更好):

// 饿汉
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() {
    }
}

这种写法不但最简洁,还能轻易扩展为实例数量固定的“多例模式”。


评论

# re: 单例模式的一个疑问[未登录]  回复  更多评论   

2012-01-11 08:33 by test
public enum HungrySingleton 不会延迟加载

# re: 单例模式的一个疑问  回复  更多评论   

2012-01-11 10:15 by 蜀山兆孨龘
@test
只有在第一次用 HungrySingleton 的时候才会加载其单例对象。如果代码里面不使用这个枚举,根本不会加载。这还不叫延迟加载?

# re: 单例模式的一个疑问  回复  更多评论   

2012-01-11 11:45 by Sakura
enum是用了java的语法特性

我们开发组用单例模式时,将实例缓存下来:
private ModelLocator model = ModelLocator.getInstance();
每次用时都直接用model,而不是重复的调用getInstance()
保持这种用法约定的话就达到了Lazy的优势。
对象只有第一次使用时才加载,加载后保存在private 成员变量中,
以后每次用时不会再调用getInstance(),即不会参与多余的判断和同步的性能消耗。
如果不Lazy的话,采用框架启动扫描类时会慢

java的语法特性,完全可以用enum来实现非Lazy加载,代码简洁、可读性高

# re: 单例模式的一个疑问  回复  更多评论   

2012-01-11 13:21 by 蜀山兆孨龘
@Sakura
你这种写法确实达到了延迟的效果,但我并不觉得它有什么大的优势,除非在你的系统中有相当一部分单例类很难被调用——这几乎不可能,早晚都会全部加载。

系统的启动时间长一点没关系,在运行的时候时不时卡一下(延迟加载单例)就不太友好了。

# re: 单例模式的一个疑问[未登录]  回复  更多评论   

2012-01-12 09:28 by test
加载HungrySingleton类的时候就会实例化,这个不叫延迟加载
加载类的时候不实例化而是真正用到实例的时候实例化才叫延迟加载

# re: 单例模式的一个疑问  回复  更多评论   

2012-01-12 10:24 by 蜀山兆孨龘
@test
当你使用一个单例类的时候,第一步不是调用 getInstance() 吗?莫非你还在单例类里面写了不少静态工具方法?当然,除非你正在做一个会对类进行扫描的框架。

只有注册用户登录后才能发表评论。


网站导航: