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)和主类初始化中相互依赖的对象

Java 类加载之匿名类和主类相互依赖问题的更多相关文章

  1. java基础,继承类题目:编写一个Java应用程序,该程序包括3个类:Monkey类、People类和主类 E

    21.编写一个Java应用程序,该程序包括3个类:Monkey类.People类和主类 E.要求: (1) Monkey类中有个构造方法:Monkey (String s),并且有个public vo ...

  2. JAVA之中出现无法加载主类的情况解决方法

    j今天打代码的时候出现了无法加载主类的情况,我就收集了一些,java无法加载主类的方法 ava无法加载主类解决办法 今天启动项目,又遇到找不到或无法加载主类的情况,清除项目后无法编译,class文件下 ...

  3. 21.编写一个Java应用程序,该程序包括3个类:Monkey类、People类和主类 E。要求: (1) Monkey类中有个构造方法:Monkey (String s),并且有个public void speak() 方法,在speak方法中输出“咿咿呀呀......”的信息。 (2)People类是Monkey类的子类,在People类中重写方法speak(),在speak方法 中输出“小样

    //Monkey类 package d922; public class Monkey { Monkey() { } Monkey (String s) { System.out.println(s) ...

  4. 6.编写一个Java应用程序,该应用程序包括2个类:Print类和主类E。Print 类里有一个方法output()功能是输出100 ~ 999之间的所有水仙花数(各位数字的 立方和等于这个三位数本身,如: 371 = 33 + 73 + 13。)在主类E的main方法中来 测试类Print。

    Print类: package com.bao; public class Print { int g,s,b; void outPut() { for(int i=100;i<1000;i++ ...

  5. 编写一个Java应用程序,该应用程序包括2个类:Print类和主类E。Print 类里有一个方法output()功能是输出100 ~ 999之间的所有水仙花数(各位数字的 立方和等于这个三位数本身,如: 371 = 33 + 73 + 13。)在主类E的main方法中来 测试类Print

    package zuoye; public class print { void output() { System.out.println("100-999之间的水仙花数是:") ...

  6. 编写一个Java应用程序,该程序包括3个类:Monkey类、People类和主类 E。要求: (1) Monkey类中有个构造方法:Monkey (String s),并且有个public void speak() 方法,在speak方法中输出“咿咿呀呀......”的信息。 (2)People类是Monkey类的子类,在People类中重写方法speak(),在speak方法 中输出“小样的,不

    package homework1; public class Monkey { //构造方法 Monkey(String s) { } //成员方法 public void speak() { Sy ...

  7. 每个程序中只有一个public类,主类?

    import java.io.*; public class GameSaverTest { public static void main(String[] args){ //创建人物 GameCh ...

  8. Monkey类、People类和主类 E。

    package jicheng; public class Monkey { private String s; public String getS() { return s; } public v ...

  9. 关于package,import,和“找不到可以加载的主类”报错之间的关系

    正在回顾java基础 目录结构如下: 一 以下代码,进入Example所在的文件夹, javac和java都不会报错 public class Example{ public static void ...

随机推荐

  1. 修复VirtualBox "This kernel requires the following features not present on the CPU: pae Unable to boot – please use a kernel appropriate for your CPU"(安装深度Linux的时候就需要)

    异常处理汇总-开发工具  http://www.cnblogs.com/dunitian/p/4522988.html 修复VirtualBox "This kernel requires ...

  2. 为什么你有10年经验,但成不了专家?(重复性刻意训练+反馈修正,练习的精髓是要持续地做自己做不好的,太精彩了)真正的高手都有很强的自学能力,老师和教练的最重要作用是提供即时的反馈(莫非我从小到大学习不好的原因在这里?没有单独刻意训练?) good

    也许简单看书就是没有刻意训练.更没有反馈,所以没有效果 我倒是想起自己,研究VCL源码的时候,都是自己给自己提问,然后苦思冥想.自己解决问题,然后Windows编程水平果然上了一个台阶.对什么叫做“框 ...

  3. 分布式存储系统GlusterFS初体验

    摘要: GlusterFS是Scale-Out存储解决方案Gluster的核心,它是一个开源的分布式文件系统,具有强大的横向扩展能力,通过扩展能够支持数PB存储容量和处理数千客户端.GlusterFS ...

  4. ThoughtWorks 面试备忘录

    ThoughtWorks 面试备忘录 前言 前段时间 ThoughtWorks 在网上和拉勾网合作搞了一次网络招聘,名为抛弃简历!让代码说话!,可谓赚足了眼球,很多程序猿纷纷摩拳擦掌.踊跃提交代码,在 ...

  5. 通过内核对象在服务程序和桌面程序之间通信的小问题 good

    关于在通过 事件对象 在服务程序和普通桌面应用程序相互之间通信的问题,分类情况进行讨论:1.普通桌面应用程序中创建事件,服务程序中打开事件 XP的情况普通桌面应用程序中创建: m_hEvent = : ...

  6. 二、OpenSceneGraph3.4第一个示例

    1.在VS2015中创建一个OSG的空解决方案,并新建一个控制台工程,取名为Example 工程结构如下图所示: 2.工程设置 "Example"->属性,打开属性选项卡,需 ...

  7. Centos 7上安装Python3.x(单版本)

    Centos7默认安装的是2.7,这里选择安装使用Python3.6.3 安装Python3.6.3 1.安装python3 需要的依赖包 yum install -y openssl-devel b ...

  8. python 之 面向对象基础(定义类、创建对象,名称空间)

    第七章面向对象 1.面向过程编程 核心是"过程"二字,过程指的是解决问题的步骤,即先干什么再干什么 基于该思想编写程序就好比在编写一条流水线,是一种机械式的思维方式 优点:复杂的问 ...

  9. 节能减排到底如何----google earth engine 告诉你!!

    (First,再次严谨说明,本人成果未经允许,切勿发表到相关学术期刊,如果有技术交流,qq1044625113,顺便打个广告,兼职GEE开发,欢迎联系!) 终于过了严寒的冬天,2017年的冬天中国南方 ...

  10. 不懂数据库索引的底层原理?那是因为你心里没点b树

    本文在个人技术博客不同步发布,详情可用力戳 亦可扫描屏幕右侧二维码关注个人公众号,公众号内有个人联系方式,等你来撩...   前几天下班回到家后正在处理一个白天没解决的bug,厕所突然传来对象的声音: ...