http://blog.csdn.net/bornforit/article/details/6898929
大家先看一個程式:
[java] view plaincopyprint?
- <SPAN style="FONT-FAMILY: SimHei">public class StaticThreadInit {
- static {
- Thread t = new Thread() {
- public void run() {
- System.out.println("進入run方法");
- System.out.println("1------" + website);
- website = "www.leegang.org";
- System.out.println("2------" + website);
- System.out.println("退出run方法");
- }
- };
- t.start();
- try {
- t.join();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- static String website = "www.crazyit.org";
-
- public static void main(String args[]) {
- System.out.println("main:" + StaticThreadInit.website);
- }
- }</SPAN>
public class StaticThreadInit {static {Thread t = new Thread() {public void run() {System.out.println("進入run方法");System.out.println("1------" + website);website = "www.leegang.org";System.out.println("2------" + website);System.out.println("退出run方法");}};t.start();try {t.join();} catch (InterruptedException e) {e.printStackTrace();}}static String website = "www.crazyit.org";public static void main(String args[]) {System.out.println("main:" + StaticThreadInit.website);}}運行結果:
進入run方法
為什麼會這樣呢?website的值為什麼沒列印出來呢?下邊我們來分析下這段程式的執行過程:
main線程試圖訪問StaticThreadInit.website的值,此時StaticThreadInit尚未被初始化,因此main線程開始對該類執行初始化。初始化過程主要分為兩個步驟:一是為該類所有靜態field分配記憶體,二是調用靜態初始化塊的代碼執行初始化。因此main線程首先會為StaticThreadInit類的website field分配記憶體空間,此時的website的值為null,接著,main線程開始執行StaticThreadInit類的靜態初始化塊。該代碼塊建立並啟動一個新的線程,並調用了新線程的join()方法,這意味著main線程必須等待新線程執行結束後才能向下執行。新線程開始執行之後,首先執行System.out.println("進入run方法");代碼,這就是運行該程式時看到的第一行輸出。接著程式試圖執行System.out.println(website);問題出現了,StaticThreadInit類正由main線程執行初始化,因此新線程會等待main線程對StaticThreadInit類執行初始化結束。這時候就滿足了死結條件:兩個線程互相等待對方執行,因此都不能向下執行。因此程式執行到此就出現了死結,程式沒法執行下去了。
出現死結的關鍵原因是程式調用了t.join()。下面分析將t.join()注釋後的程式運行情況:
main:www.crazyit.org
進入run方法
1------www.crazyit.org
2------www.leegang.org
退出run方法
下面分析下執行過程:
main線程進入StaticThreadInit靜態初始化之後,同樣建立並啟動新的線程,這次主線程不會等待新線程,此時新線程只是處於就緒狀態,還未進入運行狀態。main線程繼續執行初始化操作,當完成初始化操作之後,main中的輸出語句也就可以完成了。接下來新線程才進入運行狀態,一次執行run方法。很明顯,產生上面運行結果的原因是調用一條線程的start()方法後,該線程並不會立即進入運行狀態,他將只是保持在就緒狀態。
為了改變這種狀態,在t.start()之後立即調用Thread.sleep()暫停當前程式線程,使得新線程立即獲得執行的機會,運行結果:
進入run方法
main:www.crazyit.org
1------www.crazyit.org
2------www.leegang.org
退出run方法
下面分析下執行過程:
當main線程建立啟動一條新的線程時,然後主線程調用Thread.sleep()暫停自己,使得新線程獲得執行機會,但當新線程執行輸出website的值時,因為StaticThreadInit類還未初始化完成,因此新線程不得不放棄執行。線程調度器再次切換到main線程,mian線程於是完成website的初始化,至此StaticThreadInit類初始化完成。通常main線程不會立即切換回來執行新的線程,它會執行main方法裡的第一行代碼。
這裡有一個實際問題:靜態初始化塊裡多線程對靜態field所賦的值根本不是初始值,它只是一次普通的賦值。再看下邊的代碼:
[java] view plaincopyprint?
- <SPAN style="FONT-FAMILY: SimHei"><SPAN style="FONT-SIZE: 16px">public class StaticThreadInit {
- static {
- Thread t = new Thread() {
- public void run() {
- website = "www.leegang.org";
- }
- };
- t.start();
- try {
- Thread.sleep(1);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- final static String website;
- public static void main(String args[]) {
- System.out.println("main:" + StaticThreadInit.website);
- }
- }</SPAN></SPAN>
public class StaticThreadInit {static {Thread t = new Thread() {public void run() {website = "www.leegang.org";}};t.start();try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}final static String website;public static void main(String args[]) {System.out.println("main:" + StaticThreadInit.website);}} 編譯上面的程式就會出錯:無法為最終變數website指定值。
從上面的錯誤提示可以看出:靜態初始化塊啟動的新線程根本不允許為website賦值,這表明,新線程為website的賦值根本不是初始化操作,只是一次普通的賦值。這個程式給我們的教訓是:分析一個程式不能僅僅停留在靜態代碼上,而是應該從程式執行過程來把握程式的運行細節。
不要認為所有放在靜態初始化塊中的代碼就一定是類初始化操作,靜態初始化塊中啟動新線程的run方法只是新線程的線程執行體,並不是類初始化操作。類似地,不要認為所有在非靜態初始化塊中的代碼就一定是對象初始化操作,非靜態初始化塊中啟動新的線程的run方法只是新線程的線程執行體,並不是對象初始化操作。