题目
在小马哥的每日一问中看到了一道这个题:输出什么?。当时看错了在static块中的代码,就毫不意外的答错了= =,这个题其实没有看起来那么简单,这里去记录下这个题。小马哥这个每日一题的系列有很多比较"坑"的题,一般第一遍都比较难答对,推荐每天没事的时候可以去思否上看看这个题,也算拾遗一些基础~
再来看看这个问题的代码:
public class Lazy {
private static boolean initialized = false; static {
Thread t = new Thread(() -> initialized = true); t.start(); try {
t.join(); } catch (InterruptedException e) {
throw new AssertionError(e); } } public static void main(String[] args) {
System.out.println(initialized); } }
讯享网
这个题问的是最后输出的什么。一开始很想当然的就去想输出什么,但是最后在ide中试了下运行,发现启动就卡在了那里_(:з」∠)…
后面就去用jstack看了下线程的情况:
讯享网2019-08-03 20:23:45 Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.171-b11 mixed mode): "Thread-0" #10 prio=5 os_prio=31 tid=0x00007fece71eb800 nid=0x3d03 in Object.wait() [0x0000bbb000] java.lang.Thread.State: RUNNABLE at 函数式设计.设计.Lazy$$Lambda$1/.run(Unknown Source) at java.lang.Thread.run(Thread.java:748) "main" #1 prio=5 os_prio=31 tid=0x00007fece nid=0x1703 in Object.wait() [0x0000c8e000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x00000007956ffb30> (a java.lang.Thread) at java.lang.Thread.join(Thread.java:1252) - locked <0x00000007956ffb30> (a java.lang.Thread) at java.lang.Thread.join(Thread.java:1326) at 函数式设计.设计.Lazy.<clinit>(Lazy.java:11) JNI global references: 320
发现Thread-0是Runnable状态的,但是是in object.wait() 这里还是卡住了没有执行。
思考
这个题里有几个点:
(1)static块也是main线程去加载的
(2)匿名内置类和lambda是有区别的
这里去简单说明下,如果在线程中用的是new Runnable的匿名内置类的方式:
static {
println("static模块加载了"); Thread t = new Thread( // new Runnable 匿名内置类是 通过 Lazy$1.class来实现的 new Runnable() {
@Override public void run() {
} } ); t.start(); try {
t.join(); } catch (InterruptedException e) {
throw new AssertionError(e); } }
也就是在看编译生成的字节码目录中会多一个Lazy$1.class文件:
并且在反编译Lazy中看到static块中,依赖这个Lazy$1.class的init方法。


而如果是使用的是像题目中的lambda表达式方式,可以看到字节码文件中并没有Lazy$1.class,而是在反编译class文件中的字节码中多了invokeDynamic指令来实现的lambda表达式:

如果是匿名内之类的方式
我们先看如果是换成Runnable匿名内置类方式,而实现的run方法是个空方法体,即代码为:
讯享网 private static boolean initialized = false; // static也是由main线程去初始化的 static {
println("static模块加载了"); Thread t = new Thread( // new Runnable 匿名内置类是 通过 Lazy$1.class来实现的 new Runnable() {
@Override public void run() {
System.out.println("匿名内置类执行"); } } ); t.start(); try {
t.join(); } catch (InterruptedException e) {
throw new AssertionError(e); } } public static void main(String[] args) {
println("main线程执行了"); System.out.println(initialized); } private static void println(Object o) {
System.out.printf("线程[%s]- %s\n", Thread.currentThread().getName(), o); }
这时启动并不会hang住;将run方法中加入了对static变量initialized的修改或者调用private static方法println,即代码为:
@Override public void run() {
System.out.println("匿名内置类执行"); // 调用 static变量赋值或者static方法就会发生类似于死锁的现象 因为静态变量算这个类的一部分 initialized = true; // println("static方法 打印线程名称执行"); }
再次启动,会发现也hang住出现死锁现象。
其实从上面三点就可以分析出,因为在static模块执行时(Lazy类是不完全初始化的),这时Runnable类也随之初始化,如果在Runnable类(也就是Lazy$1.class)初始化的时候,还依赖了Lazy的静态变量或者静态方法,那么就会产生字节码直接的循环依赖。
可以在下图中看到字节码中invokestatic指令代表依赖了Lazy的静态内容初始化完成:

再看回这道题
如果是lambda表达式,即使run方法中是空实现(即不在run方法中引用static变量或者static方法),启动也会hang住,这说明lambda来初始化线程并不受是否引用了static内容影响。
这里是因为 invokedDynamic指令是Lazy字节码的一部分,不需要因为引用static方法或者变量来执行,它需要等待Lazy类初始化的完成,而本身初始化完成又依赖invokedDynamaic指令的执行,同时执行的是字节码方法符为run:()Ljava/lang/Runnable,是执行自己的run方法,所以在字节码上也是一个循环依赖。(类加载器loadClass是同步的)。
这里注意下:这里不是只要用了invokeDynamic指令就会发生这个问题,比如方法引用也是通过invokeDynamic指令实现的如果在run方法中使用的是代码:
讯享网static {
println("static模块加载了"); Thread t = new Thread( // 方法引用 System.out::println ); t.start(); try {
t.join(); } catch (InterruptedException e) {
throw new AssertionError(e); } }
但是启动就不会有问题,因为这个等待的是java.io.PrintStream这和类初始化,而这个类初始化是BootStrap类加载器初始化的,早于Lazy类初始化加载,所以能正常运行。
也就是说,在static代码块中:
- 当使用匿名内置类的时候,注意不要依赖外部类的静态变量或者方法
- 当使用lambda表达式或者方法引用,注意类的加载的先后顺序,如果依赖不当,会造成启动死锁的情况。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/70256.html