欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2023-11-16 16:38:28
Qestion Answer A: B: C: 编译错误 D: 以上答案都是错的 Explain 程序执行的时候,App Classloader 会首先加载 , 按照类的顺序依次执行。 CASE 1 我们都知道, 块会在类加载的时候初始化,那么下一步会执行到 我们先来看一下当前行的字节码: 分析 可 ......

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指令的在当前的执行又依赖于当前所处的主类,主类并没有执行结束,因此它需要等待主类执行结束,因此会在此停顿,如下:

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

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 一个匿名类,那么它的结果呢?

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

依然还是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 代码块执行。");,那么结果呢?

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

执行成功的返回了。为什么?继续查看字节码

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