Java类谜题52:合计数的玩笑

文章作者 100test 发表时间 2007:03:10 18:36:22
来源 100Test.Com百考试题网


下面的程序在一个类中计算并缓存了一个合计数,并且在另一个类中打印了这个合计数。那么,这个程序将打印出什么呢?这里给一点提示:你可能已经回忆起来了,在代数学中我们曾经学到过,从1到n的整数总和是n(n 1)/2。
class Cache {
static {
initializeIfNecessary().
}
private static int sum.
public static int getSum() {
initializeIfNecessary().
return sum.
}

private static boolean initialized = false.
private static synchronized void initializeIfNecessary() {
if (!initialized) {
for (int i = 0. i < 100. i )
sum = i.
initialized = true.
}
}
}
public class Client {
public static void main(String[] args) {
System.out.println(Cache.getSum()).
}
}

草草地看一遍,你可能会认为这个程序从1加到了100,但实际上它并没有这么做。再稍微仔细地看一看那个循环,它是一个典型的半开循环,因此它将从0循环到99。有了这个印象之后,你可能会认为这个程序打印的是从0到99的整数总和。用前面提示中给出的公式,我们知道这个总和是99×100/2,即4,950。但是,这个程序可不这么想,它打印的是9900,是我们所预期值的整整两倍。是什么导致它如此热情地翻倍计算了这个总和呢?
该程序的作者显然在确保sum在被使用前就已经在初始化这个问题上,经历了众多的麻烦。该程序结合了惰性初始化和积极初始化,甚至还用上了同步,以确保缓存在多线程环境下也能工作。看起来这个程序已经把所有的问题都考虑到了,但是它仍然不能正常工作。它到底出了什么问题呢?
与谜题49中的程序一样,该程序受到了类初始化顺序问题的影响。为了理解其行为,我们来跟踪其执行过程。在可以调用Client.main之前,VM必须初始化Client类。这项初始化工作异常简单,我们就不多说什么了。Client.main方法调用了Cache.getsum方法,在getsum方法可以被执行之前,VM必须初始化Cache类。

回想一下,类初始化是按照静态初始器在源代码中出现的顺序去执行这些初始器的。Cache类有两个静态初始器:在类顶端的一个static语句块,以及静态域initialized的初始化。静态语句块是先出现的,它调用了方法initializeIfNecessary,该方法将测试initialized域。因为该域还没有被赋予任何值,所以它具有缺省的布尔值false。与此类似,sum具有缺省的int值0。因此,initializeIfNecessary方法执行的正是你所期望的行为,将4,950添加到了sum上,并将initialized设置为true。
在静态语句块执行之后,initialized域的静态初始器将其设置回false,从而完成Cache的类初始化。遗憾的是,sum现在包含的是正确的缓存值,但是initialized包含的却是false:Cache类的两个关键状态并未同步。
此后,Client类的main方法调用Cache.getSum方法,它将再次调用initializeIfNecessary方法。因为initialized标志是false,所以initializeIfNecessary方法将进入其循环,该循环将把另一个4,950添加到sum上,从而使其值增加到了9,900。getSum方法返回的就是这个值,而程序打印的也是它。
很明显,该程序的作者认为Cache类的初始化不会以这种顺序发生。由于不能在惰性初始化和积极初始化之间作出抉择,所以作者同时运用这二者,结果产生了大麻烦。要么使用积极初始化,要么使用惰性初始化,但是千万不要同时使用二者。

相关文章


Java类谜题53:按你的意愿行事
Java类谜题51:那个点是什么
Java类谜题52:合计数的玩笑
Java类谜题50:不是你的类型
Java类谜题49:比生命更大
澳大利亚华人论坛
考好网
日本华人论坛
华人移民留学论坛
英国华人论坛