Java 类加载之匿名类和主类相互依赖问题
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 类加载之匿名类和主类相互依赖问题的更多相关文章
- java基础,继承类题目:编写一个Java应用程序,该程序包括3个类:Monkey类、People类和主类 E
21.编写一个Java应用程序,该程序包括3个类:Monkey类.People类和主类 E.要求: (1) Monkey类中有个构造方法:Monkey (String s),并且有个public vo ...
- JAVA之中出现无法加载主类的情况解决方法
j今天打代码的时候出现了无法加载主类的情况,我就收集了一些,java无法加载主类的方法 ava无法加载主类解决办法 今天启动项目,又遇到找不到或无法加载主类的情况,清除项目后无法编译,class文件下 ...
- 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) ...
- 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++ ...
- 编写一个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之间的水仙花数是:") ...
- 编写一个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 ...
- 每个程序中只有一个public类,主类?
import java.io.*; public class GameSaverTest { public static void main(String[] args){ //创建人物 GameCh ...
- Monkey类、People类和主类 E。
package jicheng; public class Monkey { private String s; public String getS() { return s; } public v ...
- 关于package,import,和“找不到可以加载的主类”报错之间的关系
正在回顾java基础 目录结构如下: 一 以下代码,进入Example所在的文件夹, javac和java都不会报错 public class Example{ public static void ...
随机推荐
- 微信小程序实战之天气预报
原文:微信小程序实战之天气预报 这个案例是仿UC中天气界面做的中间也有点出入,预留了显示当前城市名字和刷新图标的位置,自己可以写下,也可以添加搜索城市.值得注意的是100%这个设置好像已经不好使了,可 ...
- 学习Java,容易被你忽略的小细节(2)
昨天心情真的太糟糕了,写完<学习Java,值得注意你注意的问题(1)>之后,迎来些许的支持以后就是一片片的谴责.我的主页上涌现出许许多多Java方面的牛人,谴责我水平太低,写的问题太初级. ...
- IT职场初体验一
自己学习计算机专业也算有两个年头了吧,对于这个刚刚IT入门的菜鸟,对IT职场充满了好奇和憧憬,本人大学也像很多大学生一样,进入计算机专业也不是自己最初想进入的专业,进入这个原本离自己有点遥远的行业,一 ...
- SYN6107型 GPS北斗双模子钟
SYN6107型 GPS北斗双模子钟 产品概述 SYN6107型GPS北斗双模子钟是由西安同步电子科技有限公司精心设计.自行研发生产的一套以接收北斗卫星信号的子钟,从北斗地球同步卫星上获取标准时钟信号 ...
- github 上传更新代码(最简单的方法)
1.首先你需要一个github账号,还没有的朋友先去注册一个吧! GitHub地址:https://github.com/ 我们使用git需要先安装git工具,这里给出下载地址,下载后一路直接安装即可 ...
- spring boot 2.x 系列 —— spring boot 实现分布式 session
文章目录 一.项目结构 二.分布式session的配置 2.1 引入依赖 2.2 Redis配置 2.3 启动类上添加@EnableRedisHttpSession 注解开启 spring-sessi ...
- ParrotSec 中文社区 QQ群认证 Openssl解密
ParrotSec 中文社区 QQ群认证 Openssl解密 下载Key.txt 打开parrot 系统,复制文件到系统.打开命令行输入 openssl enc -aes-256-cfb -d -in ...
- 经典Java笔试面试题
面向对象编程(OOP) Java是一个支持并发.基于类和面向对象的计算机编程语言.下面列出了面向对象软件开发的优点: 代码开发模块化,更易维护和修改. 代码复用. 增强代码的可靠性和灵活性. 增加代码 ...
- node.js的异步I/O、事件驱动、单线程
nodejs的特点总共有以下几点 异步I/O(非阻塞I/O) 事件驱动 单线程 擅长I/O密集型,不擅长CPU密集型 高并发 下面是一道很经典的面试题,描述了node的整体运行机制,相信很多人都碰到了 ...
- kubernetes实战篇之helm示例yaml文件文件详细介绍
系列目录 前面完整示例里,我们主要讲解helm打包,部署,升级,回退等功能,关于这里面的文件只是简单介绍,这一节我们详细介绍一下这里面的文件,以方便我们参照创建自己的helm chart. Helm ...