跟我学(Effective Java 2)第71条:慎用延迟初始化

第71条:慎用延迟初始化

延迟初始化(lazy initialization)是延迟到需要域的值时才将它初始化的这种行为。如果 永远不需要这个值,这个域就永远不会被初始化。这种方法既适用于静态域,也适用于实例域。虽然延迟初始化主要是一种优化,但它也可以用来打破类和实例初始化中的有害循环。

就像大多数的优化一样,对于延迟初始化,最好建议“除非绝对必要,否则就不要这么做”( 见第55条:谨慎的进行优化)。延迟初始化就像一把双刃剑。它降低了初始化类或者创建实例的开销,却增加了访问被延迟初始化的域的开销。根据延迟初始化的域最终需要初始化的比例、初始化这些 域要多少开销,以及每个域多久被访问一次,延迟初始化(就像其他的许多优化一样)实际上降低了性能。

也就是说,延迟初始化有它的好处。如果域只在类的实例部分被访问,井且初始化这个域的开销很高,可能就值得进行延迟初始化。要确定这一点,唯一的办法就是测量类在用和不用延迟初始化时的性能差别。

当有多个线程时,延迟初始化是需要技巧的。如果两个或者多个线程共享一个延迟初始化的域,采用某种形式的同步是很重要的,否则就可能造成严重的Bug(见第66条:同步访问共享的可变数据)。

在大多数情况下,正常的初始化要优先于延迟初始化。下面是正常初始化的实例域的一个 典型声明。注意其中使用了final修饰符(见第15条)。

private final FieldType field = computeFieldValue();

如果利用延迟优化来破坏初始化的循环,就要使用同步访问方法,因为它是最简单、最清楚的替代方法:

synchronized FieldType getField(){
    if(field == null){
        field = computeFieldValue();
    }
    return field;
}

这两种习惯模式(正常的初始化和使用了同步访问方法的延迟初始化)应用到静态域上时保持不变,除了给域和访问方法声明添加了static修饰符之外。

如果出于性能的考虑而需要对静态域使用延迟初始化,就使用 lazy initialization holder class模式。(典型的内部类静态域单例模式)这种模式保证了类要到被用到的时候才会被初始化。

private static class FieldHolder{
    static final FieldType field = computeFieldValue();
}
static FieldType getField(){
    return FieldHolder.field;
}

当getField方法第一次被调用时,它第一次读取FieldHolder.field,导致FieldHolder类得到 初始化。这种模式的魅力在于,getField方法没有被同步,并且只执行一个域访问,因此延迟 初始化实际上并没有增加任何访问成本。现代的VM将在初始化该类的时候,同步域的访问。—旦这个类被初始化,VM将修补代码,以便后续对该域的访问不会导致任何测试或者同步。

如果出于性能的考虑而需要对实例域使用延迟初始化,就使用双重检查模式(double–check idiom)。这种模式避免了在域被初始化之后访问这个域时的锁定开销 (见第 67条:避免过度同步)。这 种模式背后的思想是:两次检査域的值[因此名字叫双重检查(double-check)],第一次检査 时没有锁定,看看这个域是否被初始化了,第二次检査时有锁定。只有当第二次检査时表明这个域没有被初始化,才会调用computeFieldValue方法对这个域进行初始化。因为如果域已 经被初始化就不会有锁定,域被声明为volatile很重要(见第66条:同步访问共享的可变数据)。

下面就是这种习惯模式:(又是一种经典单例模式)

class SynchronizedSingleton {

	//一个静态的实例
	private SynchronizedSingleton synchronizedSingleton;
	//私有化构造函数
	private SynchronizedSingleton(){}
	//给出一个公共的静态方法返回一个单一实例
	public SynchronizedSingleton getInstance(){
		if (synchronizedSingleton == null) {
			synchronized (this) {
				if (synchronizedSingleton == null) {
					synchronizedSingleton = new SynchronizedSingleton();
				}
			}
		}
		return synchronizedSingleton;
	}
}

简而言之,大多数的域应该正常地进行初始化,而不是延迟初始化。如果为了达到性能目标,或者为了破坏有害的初始化循环,而必须延迟初始化一个域,就可以使用相应的延迟初始化方法。对于实例域,就使用双重检查模式(double-check idiom),对于静态域,则使用lazy initialization holder class idiom。(内部类静态域单例模式)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值