近日看到一道奇葩的面试题:同一个线程对象调用 2 次 start 方法会怎样?

问出这个问题的人也比较无聊,真的有人会在代码中连续调用 2 次 start 方法吗?

从这个问题也引发了另外一个问题,为什么 Java 要限制同一个线程对象不能调用两次 start 方法,于是带着好奇心,看了一下源码,分析了一下这个问题。我们先看一段示例,你能说出程序运行结果吗?

public class ThreadStartExample {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("Thread is running");
        });

        // 第一次调用 start 方法,线程正常启动
        thread.start();

        // 尝试第二次调用 start 方法
        thread.start();
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

运行上述代码将输出:

Thread is running
抛出异常 java.lang.IllegalThreadStateException: Thread already started
  • 1.
  • 2.

在 Java 中,Thread 类的 start 方法用于启动一个新线程,我们来看一下相关源码。

public synchronized void start() {
    if (threadStatus != 0)
        throw new IllegalThreadStateException();
    group.add(this);
    start0();
    if (stopBeforeStart) {
        stop0(throwableFromStop);
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

threadStatus 是线程的状态变量,用于跟踪线程的生命周期状态,当线程已经启动时 threadStatus 将不再是 0,再次调用 start 方法时将抛出 IllegalThreadStateException 异常。

group.add(this) 方法将当前线程添加到线程组中。线程组用于管理一组线程,可以对其进行批量操作(如中断所有线程)。而 start0 是一个 native 方法用于启动线程。

因此当我们第一次调用 start 方法时,线程进入就绪(Runnable)状态,并等待线程调度器分配 CPU 时间片来执行其 run 方法。

如果对同一个线程对象再次调用 start 方法,Java 虚拟机会抛出 IllegalThreadStateException 异常。这是因为一个线程对象只能被启动一次,第二次调用 start 方法是非法的操作。

为什么 Java 要限制同一个线程对象不能调用两次 start 方法

Java 中的线程有一个明确的生命周期,从创建到终止,线程只能经历一次完整的生命周期。线程的生命周期有以下几个状态:

  • NEW:线程对象已创建,但尚未启动。
  • RUNNABLE:线程正在运行或准备运行。
  • BLOCKED:线程被阻塞,等待监视器锁。
  • WAITING:线程等待另一个线程执行特定操作。
  • TIMED_WAITING:线程等待指定时间。
  • TERMINATED:线程已退出。

当一个线程从 NEW 状态变为 RUNNABLE 状态后,它的生命周期已经开始了,直到它进入 TERMINATED 状态。线程生命周期的管理需要确保线程状态的一致性。

如果允许对同一个线程对象调用两次 start 方法,可能会导致线程状态的不一致和不可预测的行为:

  • 线程可能会被多次启动,导致同一个线程对象在多个 CPU 核心上并行执行,破坏了线程的状态一致性。
  • 线程的资源管理和清理将变得复杂和难以维护,增加了系统的复杂性和出错的可能性。

另外 Java 的线程模型设计遵循单一职责原则,一个线程对象的职责是完成一次任务。允许线程对象被多次启动违背了这一设计原则,增加了线程对象的职责和复杂性。这一限制确保了线程生命周期和状态管理的正确性和一致性。