Qestion
/**
* ClassInitializedOrder for : Java Classload Order Test
*
* @author <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang | 若初</a>
* @since 2019/7/20
*/
// CASE 1
public class ClassInitializedOrder {
private static boolean initialized = false;
static {
println("static 程式碼塊執行。");
Thread thread = new Thread(() -> initialized = true);
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
println("main 函式執行。");
System.out.println("initialized = " + initialized);
}
private static void println(Object o){
System.out.println(o);
}
}
-------------------------------------------------------------------
// CASE 2
public class ClassInitializedOrder {
private static boolean initialized = false;
static {
println("static 程式碼塊執行。");
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
println("Runnable 程式碼塊執行。");
initialized = true;
}
});
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
println("main 函式執行。");
System.out.println("initialized = " + initialized);
}
private static void println(Object o){
System.out.println(o);
}
Answer
- A:
initialized = true
- B:
initialized = false
- C: 編譯錯誤
- D: 以上答案都是錯的
Explain
程式執行的時候,App Classloader 會首先載入ClassInitializedOrder.class
, 按照類的順序依次執行。
private static boolean initialized = false;
CASE 1
我們都知道,static
塊會在類載入的時候初始化,那麼下一步會執行到Thread thread = new Thread(() -> initialized = true);
我們先來看一下當前行的位元組碼:
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=3, locals=2, args_size=0
0: iconst_0
1: putstatic #7 // Field initialized:Z
4: new #11 // class java/lang/Thread
7: dup
8: invokedynamic #12, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;
13: invokespecial #13 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
16: astore_0
17: aload_0
18: invokevirtual #14 // Method java/lang/Thread.start:()V
21: aload_0
22: invokevirtual #15 // Method java/lang/Thread.join:()V
25: goto 33
28: astore_1
29: aload_1
30: invokevirtual #17 // Method java/lang/InterruptedException.printStackTrace:()V
33: return
分析#12
可以看到當前行的處理需要()
也就是改匿名類本身來處理,InvokeDynamic
指令的在當前的執行又依賴於當前所處的主類,主類並沒有執行結束,因此它需要等待主類執行結束,因此會在此停頓,如下:
CASE 2
繼續檢視位元組碼:
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=4, locals=2, args_size=0
0: iconst_0
1: putstatic #1 // Field initialized:Z
4: ldc #14 // String static 程式碼塊執行。
6: invokestatic #2 // Method println:(Ljava/lang/Object;)V
9: new #15 // class java/lang/Thread
12: dup
13: new #16 // class com/sxzhongf/daily/question/july/ClassInitializedOrder$1
16: dup
17: invokespecial #17 // Method com/sxzhongf/daily/question/july/ClassInitializedOrder$1."<init>":()V
20: invokespecial #18 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
23: astore_0
24: aload_0
25: invokevirtual #19 // Method java/lang/Thread.start:()V
28: aload_0
29: invokevirtual #20 // Method java/lang/Thread.join:()V
32: goto 40
35: astore_1
36: aload_1
37: invokevirtual #22 // Method java/lang/InterruptedException.printStackTrace:()V
40: return
檢視#16
,我們可以看到這裡變成了new #16 // class com/sxzhongf/daily/question/july/ClassInitializedOrder$1
,可以明顯看到從之前的invokeDynamic
變成了 new 一個匿名類,那麼它的結果呢?
依然還是block.我們來換一行程式碼試試?
public class ClassInitializedOrder {
private static boolean initialized = false;
static {
println("static 程式碼塊執行。");
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//println("Runnable 程式碼塊執行。");
System.out.println("Runnable 程式碼塊執行。");
//initialized = true;
}
});
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
我們看到我們只是修改了一行程式碼System.out.println("Runnable 程式碼塊執行。");
,那麼結果呢?
執行成功的返回了。為什麼?繼續檢視位元組碼
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=4, locals=2, args_size=0
0: iconst_0
1: putstatic #9 // Field initialized:Z
4: ldc #14 // String static 程式碼塊執行。
6: invokestatic #3 // Method println:(Ljava/lang/Object;)V
9: new #15 // class java/lang/Thread
12: dup
13: new #16 // class com/sxzhongf/daily/question/july/ClassInitializedOrder$1
16: dup
17: invokespecial #17 // Method com/sxzhongf/daily/question/july/ClassInitializedOrder$1."<init>":()V
20: invokespecial #18 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
23: astore_0
24: aload_0
25: invokevirtual #19 // Method java/lang/Thread.start:()V
28: aload_0
29: invokevirtual #20 // Method java/lang/Thread.join:()V
32: goto 40
35: astore_1
36: aload_1
37: invokevirtual #22 // Method java/lang/InterruptedException.printStackTrace:()V
40: return
檢視#16
,看到的還是new
了一個匿名類,和上一個是一樣的,為什麼就可以成功呢?這個在於當前匿名類中沒有依賴主類的程式碼資訊。不存在上下依賴,那麼就不會出現相互等待的情況發生,當然也就不會出現block。
那麼就有朋友會問,為什麼會相互等待呢?這裡和我們join
就有關聯了,我們來看一下它的實現程式碼。
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
我們可以看到,首先它是synchronized
關鍵詞修飾的,那就說明它同時只能被一個執行緒訪問,再往下看,我們能發現,join的具體實現,其實就是wait()
來實現,當子執行緒中的程式再等待main執行緒的實現類初始化完成的時候,又依賴了主執行緒中的某些元素物件。那麼就會開始等待主執行緒初始化完成,這個時候,根據classloader載入類的執行順序,在#16
就會開始等待,那麼主類無法初始化完成,造成相互等待現相。
Result
- 匿名內建類的初始化不能依賴於外部類的初始化
- lambda表示式中
invokeDynamic
作為主類位元組碼的一部分,需要等待主類初始化完成才能開始執行
總之,在類的初始化階段,不能出現內建類(匿名/Lambda)和主類初始化中相互依賴的物件