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

  • 相关阅读:
    03-串联
    大数据项目之电商数仓(3电商数据仓库系统)V6.1.3
    JQuery实现tab页
    Java面试题之计算字符/字符串出现的次数
    ios 苹果内购订单验证 --- nodejs实现
    ios 苹果内购订单验证 --- php实现
    Android内购订单验证 --- nodejs实现
    Android内购订单验证 --- php实现
    Google Compute Engine VM自动调节
    php性能优化 --- laravel 性能优化
  • 原文地址:https://www.cnblogs.com/zhangpan1244/p/11218735.html
Copyright © 2020-2023  润新知