在 Java 中,如果对同一个线程对象多次调用 start() 方法,会引发 java.lang.IllegalThreadStateException 异常。这是因为 start() 方法的设计规定,一个线程只能启动一次。
原理分析
-
线程的生命周期:
-
一个线程从创建到终止,经历以下几种状态:
- NEW(新建) :线程对象被创建,但还未调用
start()方法。 - RUNNABLE(可运行) :调用
start()方法后,线程进入就绪状态,等待 CPU 调度。 - TERMINATED(终止) :线程运行结束或异常退出后,进入终止状态。
- NEW(新建) :线程对象被创建,但还未调用
-
-
start()方法的作用:start()方法的功能是启动线程,将线程从NEW状态变为RUNNABLE状态。- 内部通过调用 JVM 的本地方法
start0()来启动线程。 - 如果线程已经不是
NEW状态,再次调用start()会导致异常。
-
为什么禁止多次调用
start()?start()方法仅能将线程从NEW状态变为RUNNABLE。- 如果允许多次调用
start(),线程生命周期的管理将变得复杂,尤其是在已经执行过的线程上重复调用时。
示例代码
java
public class ThreadStartExample {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("Thread is running...");
});
thread.start(); // 第一次调用,线程启动成功
thread.start(); // 第二次调用,抛出 IllegalThreadStateException 异常
}
}
输出:
plaintext
Thread is running...
Exception in thread "main" java.lang.IllegalThreadStateException
at java.base/java.lang.Thread.start(Thread.java:834)
at ThreadStartExample.main(ThreadStartExample.java:10)
异常根本原因
-
在
Thread类的start()方法实现中,有一个状态检查:javaif (threadStatus != 0) throw new IllegalThreadStateException();threadStatus是线程的内部状态标志,只有在NEW状态下,threadStatus为 0。- 如果线程已经启动过(
RUNNABLE、TERMINATED等状态),再次调用start()时状态不为 0,抛出异常。
start() 方法源码
以下是 JDK 8 中 Thread 类的 start() 方法核心源码:
java
public synchronized void start() {
if (threadStatus != 0) // 检查线程状态
throw new IllegalThreadStateException();
group.add(this); // 将线程添加到线程组中
boolean started = false;
try {
start0(); // 调用本地方法启动线程
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this); // 启动失败时移除线程
}
} catch (Throwable ignore) {
}
}
}
start0() 的底层实现
start0() 是一个本地方法,其底层逻辑由 JVM 的 C/C++ 代码实现。以下是 start0() 的部分行为:
3.1 线程创建(os::create_thread)
-
调用操作系统 API 创建原生线程。
-
不同操作系统的实现有所不同:
- 在 Linux 系统中,使用
pthread_create。 - 在 Windows 系统中,使用
_beginthreadex。
- 在 Linux 系统中,使用
3.2 线程栈初始化
- 为新线程分配栈内存空间,确保运行时需要的资源就绪。
- 栈空间大小由 JVM 参数(如
-Xss)控制。
3.3 调度线程执行
- 将新创建的线程交给操作系统进行调度。
- 当操作系统分配 CPU 时间片时,线程的
run()方法会开始执行。
总结
- 多次调用
start():不被允许,会抛出IllegalThreadStateException。 - 原因:线程生命周期状态的限制,一个线程对象只能被启动一次。
- 解决:如需重复执行任务,创建新的线程对象,或使用线程池(推荐)。