静态变量的初始化,你是否真的需要lazy?

看[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模式。那看看这段代码的输出:
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逻辑也能达到效果的话,那肯定郁闷坏了。引用老赵的帖的标题:如果是能简单解决的问题,就不用想得太复杂了

^ ^
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值