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,主线程如何生成?
答案 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,它是一个标准的过程。
的说明应用程序初始类的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内部)。