2011-01-07 118 views
7

在我的應用程序,還有一類象下面這樣:類初始化和同步類方法

public class Client { 
    public synchronized static print() { 
     System.out.println("hello"); 
    } 

    static { 
     doSomething(); // which will take some time to complete 
    } 
} 

這個類將在多線程環境中使用的,許多線程可同時調用Client.print()方法。我想知道是否有線程1觸發類初始化的機會,並且在類初始化完成之前,線程2進入print方法並打印出「hello」字符串?

我在生產系統(64位JVM + Windows 2008R2)中看到此行爲,但是,我無法在任何環境中使用簡單程序重現此行爲。

在Java語言規範,第12.4.1(http://java.sun.com/docs/books/jls/second_edition/html/execution.doc.html),它說:

類或接口類型T將在第一次出現之前立即初始化,如下所示:

  • T是一個類,創建了一個T的實例。
  • T是一個類,由T聲明的靜態方法被調用。
  • 指定由T聲明的靜態字段。
  • 使用由T聲明的靜態字段,對字段的引用不是編譯時常量(第15.28節)。編譯時常量的引用必須在編譯時解析爲編譯時常量的副本,所以這樣的字段的使用永遠不會導致初始化。

根據這一段,類初始化將於靜態方法的調用之前,但是,目前尚不清楚,如果類的初始化必須完成靜態方法的調用之前。根據我的直覺,在進入靜態方法之前,JVM應該要求完成類初始化,並且我的一些實驗支持我的猜測。但是,我在另一個環境中看到了相反的行爲。有人可以幫我解釋一下嗎?

任何幫助表示讚賞,謝謝。

回答

4

我引用文本的理解是,(初始化之前由T中聲明靜態方法被調用類的初始化過程完成。

將被初始化意味着初始化過程已經開始,並已終止。

所以它應該是不可能的(據我的理解),當執行靜態初始化程序時,因爲線程A調用了print,另一個線程已經可以調用print

Chapter 12.4.2 JLS描述了詳細的初始化過程,它負責初始化多線程環境中的類。

+0

靜態初始化器是一個簡單的類方法,它是在鎖(類加載器的一個)下調用的。 – bestsss 2011-01-10 20:14:15

0

如果代碼中一些容器如Servlet運行,你可以在容器的整個生命週期進行初始化。

3

執行被認爲是類初始化的一部分,靜態塊:

類的初始化包括執行其靜態初始化和初始化靜態字段(類變量)的類中聲明...

JVM規範保證這將以線程安全方式完成。引述JLS section 12.4.2 Detailed Initialization Procedure

因爲Java編程語言是多線程的,一類或接口的初始化階段需要仔細同步,因爲一些其他線程試圖在同一時間進行初始化同一類或接口。類或接口的初始化也可能被遞歸地請求作爲該類或接口的初始化的一部分;例如,類A中的一個變量初始化器可能會調用一個不相關的類B的方法,該方法可能又會調用類A的方法。Java虛擬機的實現負責照顧同步和遞歸初始化......

更詳細地,它是由類對象上獲取鎖實施:

用於初始化一個類或接口的步驟然後如下:

  1. 同步(§14.19)上類 對象,表示的類或接口 被初始化

你的方法static synchronized,它需要類對象上的鎖,以及。由於JVM在類初始化過程中獲得了相同的鎖,因此一個線程不可能初始化類,並且其他方法可能會執行static synchronized方法。 所有報價均摘自 JLS 我希望這會有幫助。順便說一句,你怎麼知道打印發生在課程初始化完成之前?

編輯:其實我錯了關於只有static synchronized不能與類初始化並行執行的假設。在類初始化完成之前,類上的任何方法都不能執行。

+0

@Petro,「由於JVM在類初始化過程中獲得了相同的鎖」,這是不準確的,因爲在JLS第12.4.2節中,它說「否則,記錄Class對象的初始化現在正在進行當前線程並釋放對象的鎖。「所以在類初始化完成之前釋放類鎖。 – nybon 2011-01-10 07:00:01

+0

您可以通過以下方式驗證此行爲:1)thread-1觸發可能需要很長時間才能完成的類初始化。 2)thread-2使用同步語句來獲取像synchronized(MyClass.class)這樣的類鎖。如果時序正常,則線程2將進入同步語句塊。 – nybon 2011-01-10 07:01:57

2

如果您的「多線程環境」使用多個類加載器來加載您的客戶端類,您可能會獲得多個客戶端實例,其中每個客戶端實例都會在運行任何Client.print()之前運行靜態初始化程序。調用。你會看到類似

doSomething 
hello 
doSomething 
hello 
hello 
hello 

我有一些示例代碼,顯示這一點,但目前的版本是有點繁瑣運行。如果你想我可以清理它併發布它。