资讯专栏INFORMATION COLUMN

Java 类加载之匿名类和主类相互依赖问题

LancerComet / 747人阅读

摘要:匿名内置类的初始化不能依赖于外部类的初始化表达式中作为主类字节码的一部分,需要等待主类初始化完成才能开始执行总之,在类的初始化阶段,不能出现内置类匿名和主类初始化中相互依赖的对象

Qestion
/**
 * ClassInitializedOrder for : Java Classload Order Test
 *
 * @author Isaac.Zhang | 若初
 * @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."":(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."":()V
        20: invokespecial #18                 // Method java/lang/Thread."":(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."":()V
        20: invokespecial #18                 // Method java/lang/Thread."":(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)和主类初始化中相互依赖的对象

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/75510.html

相关文章

  • 7. java 内部

    摘要:外部类也可以直接访问内部类的所有属性和方法。匿名内部类为局部内部类,所以局部内部类的所有限制同样对匿名内部类生效。创建内部类对象的时刻并不依赖于外围类对象的创建。内部类并没有令人迷惑的关系,他就是一个独立的实体。 基本概念 可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。 广泛意义上的内部类一般来说包括这四种: 成员内部类 局部内部类 静态内部类 匿名内部类 成...

    legendmohe 评论0 收藏0
  • Java编程中那些再熟悉不过的知识点(持续更新)

    摘要:语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。有针对不同系统的特定实现,,,目的是使用相同的字节码,它们都会给出相同的结果。项目主要基于捐赠的源代码。 本文来自于我的慕课网手记:Java编程中那些再熟悉不过的知识点,转载请保留链接 ;) 1. 面向对象和面向过程的区别 面向过程 优点: 性能比面向对象高。因为类调用时需要实例...

    taowen 评论0 收藏0
  • Spring Boot 2.x基础教程:工程结构推荐

    摘要:典型示例以下结构是比较推荐的组织方式,所有的类和其他都在之下。应用主类,该类直接位于下。默认情况下,的应用主类会自动扫描以及所有子包下的所有类来进行初始化。 Spring Boot框架本身并没有对工程结构有特别的要求,但是按照最佳实践的工程结构可以帮助我们减少可能会遇见的坑,尤其是Spring包扫描机制的存在,如果您使用最佳实践的工程结构,可以免去不少特殊的配置工作。 典型示例 以下结...

    CollinPeng 评论0 收藏0
  • 后端好书阅读与推荐

    摘要:后端好书阅读与推荐这一两年来养成了买书看书的习惯,陆陆续续也买了几十本书了,但是一直没有养成一个天天看书的习惯。高级程序设计高级程序设计第版豆瓣有人可能会有疑问,后端为啥要学呢其实就是为了更好的使用做铺垫。 后端好书阅读与推荐 这一两年来养成了买书看书的习惯,陆陆续续也买了几十本书了,但是一直没有养成一个天天看书的习惯。今天突然想要做个决定:每天至少花1-3小时用来看书。这里我准备把这...

    clasnake 评论0 收藏0
  • 后端好书阅读与推荐

    摘要:后端好书阅读与推荐这一两年来养成了买书看书的习惯,陆陆续续也买了几十本书了,但是一直没有养成一个天天看书的习惯。高级程序设计高级程序设计第版豆瓣有人可能会有疑问,后端为啥要学呢其实就是为了更好的使用做铺垫。 后端好书阅读与推荐 这一两年来养成了买书看书的习惯,陆陆续续也买了几十本书了,但是一直没有养成一个天天看书的习惯。今天突然想要做个决定:每天至少花1-3小时用来看书。这里我准备把这...

    Juven 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<