在网上混迹多年,我的ID叫"尘民1024"。"尘民"取自《灵笼·灯塔》,而"1024"是程序员心中的无限可能。祝愿大家都能从"尘民"走向"上民"。

面试间
面试官推了推眼镜,眼神锐利地盯着我:"Java线程能不能多次调用start()方法?"
我心里一紧:谁会有病调两次start()呢?尽问些没用的。
犹豫了两秒,我只好硬着头皮说:"额......理论上,start()方法只能调用一次,再调用会出错......"
面试官皱了皱眉:"还有呢?"
我支支吾吾:嗯......这个......我没仔细看过。---- 随着空气突然安静下来......
面试官:"谢谢你的回答,面试到这里就结束了。"
快速回答
同一个线程实例只能调用1 次start()
方法,多次调用会抛出 IllegalThreadStateException
。
原因 :线程状态从 NEW
转为 RUNNABLE
后不可逆,重复调用会破坏线程生命周期管理。
代码演示
java
public class StartTwiceDemo {
public static void main(String[] args) {
Thread t = new Thread(() -> System.out.println("线程运行中..."));
t.start();
t.start(); // 第二次调用会抛异常
}
}
js
Exception in thread "main" java.lang.IllegalThreadStateException
at java.lang.Thread.start(Thread.java:708)
at com.soul.yd.bk.StartTwiceDemo.main(StartTwiceDemo.java:8)
原理解析
在Thread
类的源码中,我们可以看到 start()
方法内部会检查线程的状态,如果线程的状态不为 NEW
(即未启动状态),就会抛出 IllegalThreadStateException
。这个判断 if (threadStatus != 0)
的关键作用在于确保线程只被启动一次,防止在启动后,线程状态变得不可控。
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) {
}
}
}
源码方法分析
java
/**
* 启动线程,使该线程开始执行;Java虚拟机调用该线程的run方法。
*
* 此方法的作用是使线程从新建状态(NEW)转换为就绪状态(RUNNABLE),
* 一旦获得CPU时间片,就会执行线程的run()方法。
*
* @throws IllegalThreadStateException 如果线程已经启动过
*/
public synchronized void start() {
// 检查线程状态,threadStatus不为0表示线程已经启动或已终止
// 每个线程只能启动一次,否则抛出异常
if (threadStatus != 0)
throw new IllegalThreadStateException();
// 将当前线程添加到所属的线程组中
// 线程组用于统一管理一组线程
group.add(this);
// 标记线程是否成功启动,初始为false
boolean started = false;
try {
// 调用本地方法start0(),由JVM实现,实际创建并启动操作系统线程
start0();
// 启动成功后将 started 标记为 true
started = true;
} finally {
try {
// 如果线程启动失败(started仍为false),通知线程组
// 线程组可以执行相应的失败处理逻辑
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
// 忽略在处理启动失败时可能抛出的任何异常
// 确保不会因为异常处理而影响主流程
}
}
}
// 本地方法,由JVM实现,用于实际启动线程
private native void start0();
具体来看,它防止了以下几个潜在的问题:
1. 线程的生命周期不可逆
线程在调用 start()
方法后,会从 NEW
状态进入 RUNNABLE
状态,意味着它已经提交给操作系统调度。如果再次调用 start()
,线程状态已发生变化,可能会导致:
- 重复调度:操作系统误认为线程可以重新调度,浪费资源。
- 并发问题:多个线程试图执行相同任务,可能引发资源争用和同步问题。
2. 线程状态的同步问题
start()
方法的状态改变是原子操作,使用 synchronized
确保线程安全。如果没有这个判断,可能会发生:
- 线程状态交叉:线程间状态更新不同步,导致错误的线程调度。
- 竞态条件:多个线程修改状态,造成不一致的执行流。
3. 资源管理问题
线程启动涉及操作系统资源分配,如果重复启动线程,可能会导致:
- 资源浪费:多次分配内存和 CPU 时间,影响性能。
- 死锁/活锁:重复启动的线程可能引发锁竞争,导致死锁或活锁。
4. Java 的并发模型
Java 的 Thread
类要求每个线程独立,有自己独特的状态和堆栈。多次调用 start()
会破坏这种独立性,导致:
- 线程竞争:线程间的调度和状态管理混乱,可能出现不可预见的行为。
加分项
在JVM层面,start()
方法调用的本地方法 start0()
通过操作系统的线程创建接口(如 pthread_create
在Linux中,或 CreateThread
在Windows中)来实际启动系统级线程。操作系统层面不允许同一线程句柄重复启动,这也是Java中不能多次调用 start()
的原因之一。
总结
- 结论 :同一个
Thread
实例的start()
只能调用一次,第二次会抛IllegalThreadStateException
。 - 原因:线程状态一旦从 NEW 变为 RUNNABLE,就不可逆,重复调用会破坏线程生命周期管理。是因为线程状态机设计 + 操作系统资源绑定的机制。
- 建议 :需要重复执行任务时,使用 新线程实例 或 线程池。