在学习多线程编程的过程中,初学者常常会混淆 run 方法和 start 方法这两个概念。尽管它们在功能上看似相似,但在实际应用中却有着本质的区别。这种混淆的产生主要是因为在初次尝试时,两种方法的执行效果表面上看起来并无二致,如下面的代码示例所示:
运行上述程序后,我们可以观察到以下结果:
从输出的结果来看,无论是调用 run 方法还是 start 方法,程序都能够成功完成任务。然而,如果我们进一步观察线程的名称,就会发现两者之间存在明显的差异。为了更清晰地展示这一点,我们可以在代码中添加打印当前线程名称的语句,如下所示:
执行上述代码后,程序的结果如下所示:
通过对比分析上述结果,我们可以明确地认识到:当调用 run 方法时,实际上是当前的主程序 main 在执行该方法体中的代码;而调用 start 方法才是真正创建一个新的线程来执行任务。
run 方法和 start 方法之间的第一个显著区别在于:调用 start 方法会真正地启动一个新线程来执行任务,而调用 run 方法则相当于执行一个普通的 run 方法,并不会开启新的线程。如下图所示:
run 方法和 start 方法的第二个区别在于:run 方法通常被称为线程体,它包含了具体要执行的业务逻辑代码。当调用 run 方法时,会立即执行 run 方法中的代码(前提是当前线程的时间片尚未用完);而调用 start 方法时,实际上是启动一个线程并将该线程的状态设置为就绪状态。也就是说,调用 start 方法并不会立即执行。
由于 run 方法本质上是一个普通方法,而普通方法是可以被多次调用的,因此 run 方法也可以被多次调用;而 start 方法是通过创建新线程来执行任务的,因为线程只能被创建一次,所以它们的第三个区别在于:run 方法可以被多次调用,而 start 方法只能被调用一次。
为了验证这一点,我们可以编写以下测试代码:
执行上述测试代码后,程序的结果如下所示:
从输出的结果中我们可以看到,run 方法可以被多次调用且能够正常执行,而当我们尝试第二次调用 start 方法时,程序会抛出“IllegalThreadStateException”非法线程状态异常,提示错误信息。
要理解这个问题的原因,我们需要查看 start 方法的实现源码。start 方法的源码如下所示:
从 start 方法的源码实现的第一行代码中,我们就可以找到问题的答案。因为 start 方法在执行时会首先检查当前线程的状态是否为 0,即是否为新建状态 NEW。如果当前线程的状态不是新建状态,那么就会抛出“IllegalThreadStateException”非法线程状态异常。这就是为什么 start 方法不能被重复调用的原因。
其执行过程可以描述为:当线程调用了第一个 start 方法之后,线程的状态会从新建状态 NEW 变更为就绪状态 RUNNABLE。此时,如果再次调用 start 方法,JVM 会检测到当前线程的状态已经不再是新建状态,从而抛出 IllegalThreadStateException 非法线程状态异常。
总结 run 方法和 start 方法的主要区别如下:
- 方法性质不同:run 是一个普通方法,而 start 是用于开启新线程的方法。
- 执行速度不同:调用 run 方法会立即执行任务,而调用 start 方法会将线程的状态改为就绪状态,不会立即执行。
- 调用次数不同:run 方法可以被重复调用,而 start 方法只能被调用一次。
start 方法之所以不能被重复调用,是因为线程的状态是不可逆的。在 Thread 类的 start 方法的实现源码中,进行了相应的状态检查。如果线程的状态不是新建状态 NEW,就会抛出 IllegalThreadStateException 非法线程状态异常。
是非审之于己,毁誉听之于人,得失安之于数。