共计 4857 个字符,预计需要花费 13 分钟才能阅读完成。
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)和主类初始化中相互依赖的对象