Java類謎題52:合計數(shù)的玩笑

字號:

下面的程序在一個類中計算并緩存了一個合計數(shù),并且在另一個類中打印了這個合計數(shù)。那么,這個程序?qū)⒋蛴〕鍪裁茨??這里給一點提示:你可能已經(jīng)回憶起來了,在代數(shù)學(xué)中我們曾經(jīng)學(xué)到過,從1到n的整數(shù)總和是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());
     }
    }
    草草地看一遍,你可能會認(rèn)為這個程序從1加到了100,但實際上它并沒有這么做。再稍微仔細(xì)地看一看那個循環(huán),它是一個典型的半開循環(huán),因此它將從0循環(huán)到99。有了這個印象之后,你可能會認(rèn)為這個程序打印的是從0到99的整數(shù)總和。用前面提示中給出的公式,我們知道這個總和是99×100/2,即4,950。但是,這個程序可不這么想,它打印的是9900,是我們所預(yù)期值的整整兩倍。是什么導(dǎo)致它如此熱情地翻倍計算了這個總和呢?
    該程序的作者顯然在確保sum在被使用前就已經(jīng)在初始化這個問題上,經(jīng)歷了眾多的麻煩。該程序結(jié)合了惰性初始化和積極初始化,甚至還用上了同步,以確保緩存在多線程環(huán)境下也能工作??雌饋磉@個程序已經(jīng)把所有的問題都考慮到了,但是它仍然不能正常工作。它到底出了什么問題呢?
    與謎題49中的程序一樣,該程序受到了類初始化順序問題的影響。為了理解其行為,我們來跟蹤其執(zhí)行過程。在可以調(diào)用Client.main之前,VM必須初始化Client類。這項初始化工作異常簡單,我們就不多說什么了。Client.main方法調(diào)用了Cache.getsum方法,在getsum方法可以被執(zhí)行之前,VM必須初始化Cache類。
    回想一下,類初始化是按照靜態(tài)初始器在源代碼中出現(xiàn)的順序去執(zhí)行這些初始器的。Cache類有兩個靜態(tài)初始器:在類頂端的一個static語句塊,以及靜態(tài)域initialized的初始化。靜態(tài)語句塊是先出現(xiàn)的,它調(diào)用了方法initializeIfNecessary,該方法將測試initialized域。因為該域還沒有被賦予任何值,所以它具有缺省的布爾值false。與此類似,sum具有缺省的int值0。因此,initializeIfNecessary方法執(zhí)行的正是你所期望的行為,將4,950添加到了sum上,并將initialized設(shè)置為true。
    在靜態(tài)語句塊執(zhí)行之后,initialized域的靜態(tài)初始器將其設(shè)置回false,從而完成Cache的類初始化。遺憾的是,sum現(xiàn)在包含的是正確的緩存值,但是initialized包含的卻是false:Cache類的兩個關(guān)鍵狀態(tài)并未同步。
    此后,Client類的main方法調(diào)用Cache.getSum方法,它將再次調(diào)用initializeIfNecessary方法。因為initialized標(biāo)志是false,所以initializeIfNecessary方法將進(jìn)入其循環(huán),該循環(huán)將把另一個4,950添加到sum上,從而使其值增加到了9,900。getSum方法返回的就是這個值,而程序打印的也是它。
    很明顯,該程序的作者認(rèn)為Cache類的初始化不會以這種順序發(fā)生。由于不能在惰性初始化和積極初始化之間作出抉擇,所以作者同時運用這二者,結(jié)果產(chǎn)生了大麻煩。要么使用積極初始化,要么使用惰性初始化,但是千萬不要同時使用二者。