看[url=http://www.cnblogs.com/JeffreyZhao]老赵[/url]几天前提到[url=http://www.cnblogs.com/JeffreyZhao/archive/2009/09/02/double-check-failure.html]double-check[/url],昨天又提到[url=http://www.cnblogs.com/JeffreyZhao/archive/2009/09/05/simple-over-complex.html]属性的lazy加载[/url],想起记这么一点:
在实现单例的时候,你是否真的需要用double-check之类的技巧来实现lazy创建实例?
使用单例模式但又想让它lazy创建实例,理由通常是“实例的创建可能伴随一些复杂的初始化计算,或者需要持有一些外部资源之类;如果程序中并没有使用到那个实例,那么付出的代码就白费了”。
但很多时候上述理由是个伪命题:不使用的类根本就不会被初始化。既然没有初始化,就不会付出代价去创建单例的实例。
Effective Java, 2nd推荐在Java 5或更新的版本中使用enum来实现Singleton模式。那看看这段代码的输出:
结果是:
[quote]main()
leaving main()[/quote]
虽然定义了Singleton类型,但程序中并没有使用它,所以也就没有引发它的初始化。
那么在main()里添加一行:
结果变为:
[quote]main()
Singleton()
leaving main()[/quote]
哪儿用哪儿才初始化。
只要实现Singleton的时候,该类上没有别的对外公开的静态变量或者静态方法,外界想要使用Singleton实例只能通过INSTANCE的话,那引发Singleton初始化的只有对INSTANCE的访问(这时初始化是正确的)或者通过反射(例如Class.forName(),这是例外情况)。
要是有信心代码里不会出现“例外情况”的话,又何必费神去实现double-check呢?
C#里则有些细微差异。某个类的静态初始化“想当然”也应该是初次使用的时候才进行,但实际上这取决于静态初始化逻辑是怎么写的。如果写成:
虽然代码中有Foo()使用了Singleton,但实际执行路径上没有经过Foo(),其它地方也没用过Singleton类,于是输出结果是:
[quote]Main()
leaving Main()[/quote]
但要是在Main()里添加一行:
输出结果跟想像的可能就不一样了:
[quote]Singleton()
Main()
leaving Main()[/quote]
这是因为C#中直接在静态变量声明的地方就初始化,而且没有显式提供静态构造器实现的话,会使类带上beforefieldinit标记,使得类的静态初始化提早执行。稍微改改,给Singleton类添加一个空的静态构造器的话……
会发现执行结果变为:
[quote]Main()
Singleton()
leaving Main()[/quote]
这种写法就不会使类带上beforefieldinit,于是初始化时间就跟“想像中”的一样,哪儿用到哪儿才初始化。把Instance的初始化整个挪到静态构造器里的结果也一样。
有时候费了力气去写个double-check搞不好还写错了,要是回头发现其实不用自己费神写lazy逻辑也能达到效果的话,那肯定郁闷坏了。引用老赵的帖的标题:如果是能简单解决的问题,就不用想得太复杂了
^ ^
在实现单例的时候,你是否真的需要用double-check之类的技巧来实现lazy创建实例?
使用单例模式但又想让它lazy创建实例,理由通常是“实例的创建可能伴随一些复杂的初始化计算,或者需要持有一些外部资源之类;如果程序中并没有使用到那个实例,那么付出的代码就白费了”。
但很多时候上述理由是个伪命题:不使用的类根本就不会被初始化。既然没有初始化,就不会付出代价去创建单例的实例。
Effective Java, 2nd推荐在Java 5或更新的版本中使用enum来实现Singleton模式。那看看这段代码的输出:
public class Program {
public static void main(String[] args) {
System.out.println("main()");
System.out.println("leaving main()");
}
}
enum Singleton {
INSTANCE;
Singleton() {
System.out.println("Singleton()");
}
}
结果是:
[quote]main()
leaving main()[/quote]
虽然定义了Singleton类型,但程序中并没有使用它,所以也就没有引发它的初始化。
那么在main()里添加一行:
public class Program {
public static void main(String[] args) {
System.out.println("main()");
Singleton s = Singleton.INSTANCE;
System.out.println("leaving main()");
}
}
enum Singleton {
INSTANCE;
Singleton() {
System.out.println("Singleton()");
}
}
结果变为:
[quote]main()
Singleton()
leaving main()[/quote]
哪儿用哪儿才初始化。
只要实现Singleton的时候,该类上没有别的对外公开的静态变量或者静态方法,外界想要使用Singleton实例只能通过INSTANCE的话,那引发Singleton初始化的只有对INSTANCE的访问(这时初始化是正确的)或者通过反射(例如Class.forName(),这是例外情况)。
要是有信心代码里不会出现“例外情况”的话,又何必费神去实现double-check呢?
C#里则有些细微差异。某个类的静态初始化“想当然”也应该是初次使用的时候才进行,但实际上这取决于静态初始化逻辑是怎么写的。如果写成:
using System;
public class Singleton {
public static Singleton Instance = new Singleton();
private Singleton() {
Console.WriteLine("Singleton()");
}
}
static class Program {
static void Main(string[] args) {
Console.WriteLine("Main()");
Console.WriteLine("leaving Main()");
}
static void Foo() {
var s = Singleton.Instance;
}
}
虽然代码中有Foo()使用了Singleton,但实际执行路径上没有经过Foo(),其它地方也没用过Singleton类,于是输出结果是:
[quote]Main()
leaving Main()[/quote]
但要是在Main()里添加一行:
using System;
public class Singleton {
public static Singleton Instance = new Singleton();
private Singleton() {
Console.WriteLine("Singleton()");
}
}
static class Program {
static void Main(string[] args) {
Console.WriteLine("Main()");
var s = Singleton.Instance;
Console.WriteLine("leaving Main()");
}
static void Foo() {
var s = Singleton.Instance;
}
}
输出结果跟想像的可能就不一样了:
[quote]Singleton()
Main()
leaving Main()[/quote]
这是因为C#中直接在静态变量声明的地方就初始化,而且没有显式提供静态构造器实现的话,会使类带上beforefieldinit标记,使得类的静态初始化提早执行。稍微改改,给Singleton类添加一个空的静态构造器的话……
using System;
public class Singleton {
public static Singleton Instance = new Singleton();
static Singleton() {
}
private Singleton() {
Console.WriteLine("Singleton()");
}
}
static class Program {
static void Main(string[] args) {
Console.WriteLine("Main()");
var s = Singleton.Instance;
Console.WriteLine("leaving Main()");
}
static void Foo() {
var s = Singleton.Instance;
}
}
会发现执行结果变为:
[quote]Main()
Singleton()
leaving Main()[/quote]
这种写法就不会使类带上beforefieldinit,于是初始化时间就跟“想像中”的一样,哪儿用到哪儿才初始化。把Instance的初始化整个挪到静态构造器里的结果也一样。
有时候费了力气去写个double-check搞不好还写错了,要是回头发现其实不用自己费神写lazy逻辑也能达到效果的话,那肯定郁闷坏了。引用老赵的帖的标题:如果是能简单解决的问题,就不用想得太复杂了
^ ^