[英]Java class construction under the hood
考慮我們有這樣的類:
class A {
public B b;
public void someFunc() { // called sometime
b = new B();
}
}
B
類的構造函數分配一些內部變量。
字段b
不是線程安全的,因為B
構造函數尚未完成時,另一個線程可以查看b
null
。 (在someFunc
執行期間)
我的問題是:從邏輯角度看,構造函數還沒有完成怎么辦?
對我來說,這種重新排序是不可思議的。
在線程安全的上下文中,這通常是由於即時(JIT)編譯器引起的。 JIT編譯器采用Java字節碼並將其轉換為機器碼,以使其運行更快。 在翻譯過程中,它可以進行很多優化,例如內聯各種方法和構造函數。
假設B
有一個這樣的構造函數:
class B {
int x;
B(int x) { this.x = x; }
}
內聯構造函數時,它將使用類似於以下內容的Java代碼:
b = new B(1);
並將其轉換為采用類似於以下步驟的機器代碼:
B
對象分配空間。 b
。 1
存儲在bx
。 換句話說,與此類似的代碼(在排序方面):
b = new B();
b.x = 1;
但是我們實際上根本不調用構造函數。 我們只是分配一個B
,但是JVM在內部進行分配,然后直接分配bx
。 調用構造函數將涉及跳轉指令,因此內聯它要快一些。
著名的“雙重檢查鎖定已損壞”聲明中有一個例子。
常規Java編譯器也可以內聯構造函數,但是常規Java編譯器通常不執行許多優化。
對象的實例可以從構造函數中“退出”,如下所示:
public class EscapeDemo {
static void escape(B b) {
System.out.println(b.strA);
System.out.println(b.strB); // still null in this example, even if field is final and initialized to non-null value.
}
public static void main(String[] args) {
System.out.println(new B());
}
}
class B {
final String strA;
final String strB;
B() {
strA = "some operations";
EscapeDemo.escape(this);
strB = "here";
}
}
印刷品:
some operations
null
B@hashcode
並且以類似的方式,該引用可以轉義到將在其他線程中使用它的某些代碼。
就像安迪·吉伯特(Andy Guibert)在評論中添加的那樣:寫這樣的代碼是一種不好的做法-因為它可能是許多奇怪的錯誤的源頭,並且很難跟蹤錯誤-像這里,我們有一些不應該為null的東西,但是它為null。
而且,如果您想在創建對象實例時執行某些操作,最好創建靜態工廠方法,該方法將創建實例,然后對其進行某些操作(例如添加到某個集合/注冊表),然后將其返回。
另外,如果您包括怪異的代碼使用技巧-在字節碼中,java對象的創建與構造函數的調用是分開的,因此可以從字節碼級別創建對象,將其傳遞到某個地方,然后在其他地方調用構造函數。
但是否則,在執行右側表達式后會分配字段,因此對於代碼
b = new B();
調用構造函數后,字段b
只能為null
或B
實例。 除非像我的轉義示例那樣從B
構造函數內部設置該字段。
如果我正確理解您的問題。 您正在詢問是否創建對象A
的實例,該實例的字段b
為類型B
並且在創建A
時未初始化字段b
,只有其他一些對象調用someFunc()
。 當其他線程嘗試訪問該字段b
時會發生什么?
如果是這樣,當您創建類型B
的新對象時,JVM將為此對象分配一些內存,然后將返回引用,該引用將保留在字段b
。 如果其他線程在獲取新對象的引用之前嘗試訪問字段b
,它將返回null
否則將返回對新創建對象的引用。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.