Java run()方法如何工作?

时间:2012-05-26 05:46:39

标签: java multithreading

Java中的多线程是通过定义run()和调用start()来完成的。

启动委托给通过操作系统例程启动线程的本机方法,并从这个新生成的线程中调用run()。

启动独立应用程序时,会自动创建一个主线程来执行main()。

现在考虑一下这段代码 -

public class Test extends Thread {
    public static void main(String[] args) throws Exception {
        new Thread(new Test()).start();
        throw new RuntimeException("Exception from main thread");
    }
    public void run() {
        throw new RuntimeException("Exception from child thread");
    }
}

这是输出 -

java.lang.RuntimeException: Exception from child thread
    at com.Test.run(Test.java:11)
    at java.lang.Thread.run(Thread.java:662)
java.lang.RuntimeException: Exception from main thread
    at com.Test.main(Test.java:8)

如果通过线程启动main()方法,为什么不运行()会出现在调用层次结构的顶部?

如果不实现Runnable,主线程如何生成?

4 个答案:

答案 0 :(得分:7)

  

如果通过线程启动main()方法,为什么不运行()会出现在调用层次结构的顶部?

正如其他人所说,这是因为“主”线程是特殊的。它不是通过标准Thread类机制启动,而是通过Java引导代码启动。 public static void main(String[] args)始终由本机代码中的主线程运行。

另一种解释是,实际上可能存在run()方法,但是他们构建堆栈的方式是为了不混淆用户而故意隐藏它。例如,由于您正在执行new Thread(new Test()),因此Test类实际上是target内的Thread字段。当后台Thread启动时,它实际上会调用Thread.run(),其代码为:

public void run() {
    if (target != null) {
        target.run();
    }
}

但是我们从未在堆栈框架中看到Thread.run()方法,尽管看起来它应该存在。如果用户在run()超类中覆盖它,Thread方法在堆栈框架中。它可以被JDK删除以改善堆栈帧输出。

  

Java中的多线程是通过定义run()和调用start()来完成的。

这是正确的,但对于后人我认为重要的是要意识到你的代码有问题。您的Test应该Thread,而应该实施Runnable。它有效,因为Thread实现了Runnable

您应该实现Runnable并将代码更改为以下内容:

public class Test implements Runnable {
    public static void main(String[] args) throws Exception {
        new Thread(new Test()).start();
        throw new RuntimeException("Exception from main thread");
    }
    public void run() {
        throw new RuntimeException("Exception from child thread");
    }
}

或者你仍然扩展Thread并将你开始你的线程的方式改为如下所示。建议使用上面的Runnable模式,因为它允许您的Test线程在必要时扩展另一个类。

public class Test extends Thread {
    public static void main(String[] args) throws Exception {
        new Test().start();
        throw new RuntimeException("Exception from main thread");
    }
    @Override
    public void run() {
        throw new RuntimeException("Exception from child thread");
    }
}

为什么这很重要?您当前的代码实际上是实例化2 Thread个对象,但其中只有一个是start() ed并且作为后台Thread运行。您可能会遇到以下错误:

public class Test extends Thread {
    public static void main(String[] args) throws Exception {
        Test test = new Test(); 
        new Thread(test).start();
        // this is not interrupting the background thread
        test.interrupt();

答案 1 :(得分:4)

我没有看过JVM的内部,但我猜测JVM实例化主线程来运行main方法,但是通过直接调用本机代码来运行这个主线程,而不需要经过传统的Java类以及启动线程的方法。

答案 2 :(得分:3)

main方法由JVM在一个单独的线程中启动,它是子线程的父线程,这就是为什么你不会在调用层次结构的顶部看到子线程。

所以在你的情况下,JVM创建了一个启动程序的线程,它也扩展了Thread。

然后在你的main方法中你创建了一个类的新实例,名为start on it,这将启动一个新线程,它是由JVM启动的线程的子线程,用于启动你的程序。

由于main方法是独立java程序的起点,因此JVM的责任是在一个单独的线程中启动它,你不要为它编写代码。

通过调用main方法启动程序JVM不需要它是一个Thread或实现Runnable,它是一个标准的过程。

来自Inside Java Virtual Machine

的说明
  

应用程序初始类的main()方法用作   该应用程序的初始线程的起点。最初的   线程可以反过来触发其他线程。

     

在Java虚拟机中,线程有两种形式:守护进程   和非守护进程。守护程序线程通常是由...使用的线程   虚拟机本身,例如执行垃圾的线程   采集。但是,应用程序可以标记它创建的任何线程   作为守护线程。应用程序的初始线程 - 那个   从main()开始 - 是一个非守护进程线程。

     

Java应用程序继续执行(虚拟机实例   只要任何非守护程序线程仍然存在,它就会继续存在   运行。当Java应用程序的所有非守护程序线程终止时,   虚拟机实例将退出。如果安全许可   经理,应用程序也可以通过调用它自己的死亡   类Runtime或System的exit()方法。

调用层次结构不受您的管理,它由底层线程调度程序控制。

所以例如如果我在我的机器上运行相同的代码,这就是输出

Exception in thread "main" java.lang.RuntimeException: Exception from main thread
    at TestThread.main(TestThread.java:6)
Exception in thread "Thread-1" java.lang.RuntimeException: Exception from child thread
    at TestThread.run(TestThread.java:9)
    at java.lang.Thread.run(Thread.java:662)

所以当你运行你的例子时,调度程序选择在main之前先放开子线程。

添加到@JB Nizet,如何调用程序,或者如何实现线程生命周期取决于底层操作系统和硬件,这些操作和硬件会有所不同。

没有单一的实施细节可以提供完整的答案,每个实施都会有所不同。

答案 3 :(得分:0)

也许它是一个语义参数,但是线程和进程不是同义词。

一个进程由操作系统启动,并拥有自己的私有代码页(编译的代码集)。线程从程序内部启动,最初共享其父代码页(编译的代码集)。

JVM内部使用Thread来运行main方法是环境的实现细节,但不是Java语言的表示。如果一个线程要在main堆栈帧中报告,那么引用回源代码位置的架构是不可能的,因为主服务线程没有编译单元(它在JVM内部)。