9

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

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() 方法是通过线程启动的,为什么 run() 不显示在调用层次结构的顶部?

不实现 Runnable 怎么会产生主线程?

4

4 回答 4

7

如果 main() 方法是通过线程启动的,为什么 run() 不显示在调用层次结构的顶部?

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

另一种解释是,实际上可能存在一种run()方法,但他们构建堆栈帧的方式是故意隐藏它,以免混淆用户。例如,由于您正在执行 anew Thread(new Test())那么您的Test类实际上targetThread. 当后台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 并且作为 background 运行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();
于 2012-05-26T09:37:47.960 回答
4

I haven't looked at the internals of the JVM, but I would guess that the JVM instantiates the main thread to run the main method, but runs this main thread by invoking native code directly, without going through the classical Java classes and methods to start the thread.

于 2012-05-26T06:22:12.030 回答
3

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

因此,在您的情况下,JVM 创建了一个线程来启动您的程序,该线程也扩展了 Thread。

然后在你的 main 方法中,你创建了你的类的一个新实例,在它上面调用 start,这将启动一个新线程,它是 JVM 启动的线程的子线程来启动你的程序。

由于 main 方法是独立 java 程序的起点,JVM 有责任在单独的线程中启动它,您不必为它编写代码。

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

Java虚拟机内部的描述

应用程序初始类的 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,如何调用程序或如何实现线程生命周期取决于底层操作系统和硬件,它们会并且确实会有所不同。

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

于 2012-05-26T05:49:53.323 回答
0

也许这是一个语义论点,但 Thread 和 Process 不是同义词。

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

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

于 2013-07-30T18:54:11.667 回答