开发易忽视的问题:一个线程两次调用start()方法

在 Java 中,如果对同一个线程对象多次调用 start() 方法,会引发 java.lang.IllegalThreadStateException 异常。这是因为 start() 方法的设计规定,一个线程只能启动一次。


原理分析

  1. 线程的生命周期

    • 一个线程从创建到终止,经历以下几种状态:

      • NEW(新建) :线程对象被创建,但还未调用 start() 方法。
      • RUNNABLE(可运行) :调用 start() 方法后,线程进入就绪状态,等待 CPU 调度。
      • TERMINATED(终止) :线程运行结束或异常退出后,进入终止状态。
  2. start() 方法的作用

    • start() 方法的功能是启动线程,将线程从 NEW 状态变为 RUNNABLE 状态。
    • 内部通过调用 JVM 的本地方法 start0() 来启动线程。
    • 如果线程已经不是 NEW 状态,再次调用 start() 会导致异常。
  3. 为什么禁止多次调用 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() 方法实现中,有一个状态检查:

    java 复制代码
    if (threadStatus != 0)
        throw new IllegalThreadStateException();
    • threadStatus 是线程的内部状态标志,只有在 NEW 状态下,threadStatus 为 0。
    • 如果线程已经启动过(RUNNABLETERMINATED 等状态),再次调用 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

3.2 线程栈初始化

  • 为新线程分配栈内存空间,确保运行时需要的资源就绪。
  • 栈空间大小由 JVM 参数(如 -Xss)控制。

3.3 调度线程执行

  • 将新创建的线程交给操作系统进行调度。
  • 当操作系统分配 CPU 时间片时,线程的 run() 方法会开始执行。

总结

  • 多次调用 start() :不被允许,会抛出 IllegalThreadStateException
  • 原因:线程生命周期状态的限制,一个线程对象只能被启动一次。
  • 解决:如需重复执行任务,创建新的线程对象,或使用线程池(推荐)。
相关推荐
野犬寒鸦24 分钟前
从零起步学习并发编程 || 第四章:synchronized底层源码级讲解及项目实战应用案例
java·服务器·开发语言·jvm·后端·学习·面试
!停25 分钟前
数据结构二叉树——堆
java·数据结构·算法
virus59458 小时前
悟空CRM mybatis-3.5.3-mapper.dtd错误解决方案
java·开发语言·mybatis
计算机毕设VX:Fegn08959 小时前
计算机毕业设计|基于springboot + vue蛋糕店管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
没差c10 小时前
springboot集成flyway
java·spring boot·后端
三水不滴10 小时前
Redis 过期删除与内存淘汰机制
数据库·经验分享·redis·笔记·后端·缓存
时艰.10 小时前
Java 并发编程之 CAS 与 Atomic 原子操作类
java·开发语言
编程彩机10 小时前
互联网大厂Java面试:从Java SE到大数据场景的技术深度解析
java·大数据·spring boot·面试·spark·java se·互联网大厂
笨蛋不要掉眼泪10 小时前
Spring Boot集成LangChain4j:与大模型对话的极速入门
java·人工智能·后端·spring·langchain
Yvonne爱编码10 小时前
JAVA数据结构 DAY3-List接口
java·开发语言·windows·python