恶汉式单例(鼓励的写法)
internal class MonsterManager
{
private static MonsterManager instance = new MonsterManager();
public static MonsterManager Instance
{
get { return instance; }
}
}
懒汉式单例
internal class MonsterManager
{
private static MonsterManager instance;
public static MonsterManager Instance
{
get
{
if (instance == null)
instance = new MonsterManager();
return instance;
}
}
}
这两个名词最近也是在一本设计模式书上看到,觉得挺有意思的,这边就借用下。这两种方式,本质上都是到第一个单例对象被引用时(也就是Instance对象被调用时),才会去创建对象。当然,有一点点区别,如果MonsterManager有另一个static方法存在并且被调用时,恶汉会去创建Instance,而懒汉会无动于衷。但我们一般设计单例对象时,不太会同时留有static方法功能,所以个人感觉这个初始化时机的差别可以忽略不计。
然而,懒汉存在一个缺点。如果在多线程的情况下,instance可能会被创建多次(两个线程都new了自己的instance),导致单例并不能成为真正的单例了。
改进的懒汉
internal class MonsterManager
{
private static MonsterManager instance;
public static MonsterManager Instance
{
get { return instance ?? (instance = new MonsterManager()); }
}
}
看上去这次是一行完成了取值和赋值的过程了。这样的写法是多线程安全的吗?答案是否定的。
IL_0000: nop
IL_0001: ldsfld class ConsoleApplication9.MonsterManager ConsoleApplication9.MonsterManager::'instance'
IL_0006: stloc.0
IL_0007: br.s IL_0009
IL_0009: ldloc.0
IL_000a: ret
上面是对应的IL代码,可以看出??操作符只是一个语法糖,本质上这种写法只是看上去变得很优雅,仍旧是线程不安全的。
线程安全的懒汉单例
internal class MonsterManager
{
private static MonsterManager instance = new MonsterManager();
private static readonly object lockRoot = new object();
public static MonsterManager Instance
{
get
{
if (instance == null)
{
lock (lockRoot)
{
if (instance == null)
instance = new MonsterManager();
}
}
return instance;
}
}
}
这种方法,看上去很繁琐,但实际是可用的,并且是线程安全的。需要引入一个锁变量,在new单例对象时,防止多线程的重入。这么看来,C#没有类似于JAVA的synchronized关键词,还是非常坑的。