多線程陷阱(所有靜態初始化塊中的代碼不一定是類初始化操作)

來源:互聯網
上載者:User

http://blog.csdn.net/bornforit/article/details/6898929

 

大家先看一個程式:
[java] view plaincopyprint?
 
  1. <SPAN style="FONT-FAMILY: SimHei">public class StaticThreadInit {  
  2.     static {  
  3.         Thread t = new Thread() {  
  4.             public void run() {  
  5.                 System.out.println("進入run方法");  
  6.                 System.out.println("1------" + website);  
  7.                 website = "www.leegang.org";  
  8.                 System.out.println("2------" + website);  
  9.                 System.out.println("退出run方法");  
  10.             }  
  11.         };  
  12.         t.start();  
  13.         try {  
  14.             t.join();  
  15.         } catch (InterruptedException e) {  
  16.             e.printStackTrace();  
  17.         }  
  18.     }  
  19.     static String website = "www.crazyit.org";  
  20.   
  21.     public static void main(String args[]) {  
  22.         System.out.println("main:" + StaticThreadInit.website);  
  23.     }  
  24. }</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?
  
  1. <SPAN style="FONT-FAMILY: SimHei"><SPAN style="FONT-SIZE: 16px">public class StaticThreadInit {  
  2.     static {  
  3.         Thread t = new Thread() {  
  4.             public void run() {  
  5.                 website = "www.leegang.org";  
  6.             }  
  7.         };  
  8.         t.start();  
  9.         try {  
  10.             Thread.sleep(1);  
  11.         } catch (InterruptedException e) {  
  12.             e.printStackTrace();  
  13.         }  
  14.     }  
  15.     final static String website;  
  16.     public static void main(String args[]) {  
  17.         System.out.println("main:" + StaticThreadInit.website);  
  18.     }  
  19. }</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方法只是新線程的線程執行體,並不是對象初始化操作。

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.