一个线程两次调用start()方法会出现什么情况?

典型回答

Java的线程是不允许启动两次的,第二次调用必然会抛出IllegalThreadStateException,这是一种运行时异常,多次调用start被认为是编程错误。

关于线程生命周期的不同状态,在Java 5以后,线程状态被明确定义在其公共内部枚举类型java.lang.Thread.State中,分别是:

  • NEW(新建),表示线程被创建出来还没真正启动的状态,可以认为它是个Java内部状态。
  • RUNNABLE(就绪),表示该线程已经在JVM中执行,当然由于执行需要计算资源,它可能是正在运行,也可能还在等待系统分配给它CPU片段,在就绪队列里面排队。
  • 在其它一些分析中,会额外区分一种状态RUNNING,但是从Java API的角度,并不能表示出来。
  • BLOCKED(阻塞),这个状态和我们前面介绍的同步非常相关,阻塞表示线程在等待Monitor lock。比如,线程试图通过synchronized去获取某个锁,但是其它线程已经独占了,那么当前线程就会处于阻塞状态。
  • WAITING(等待),表示正在等待其它线程采取某些操作。一个常见的场景是类似生产者消费者模式,发现任务条件尚未满足,就让当前消费者线程等待(wait),另外的生产者线程去准备任务数据,然后通过类似notify等动作,通知消费者线程可以继续工作了。Thread.join()也会令线程进入等待状态。
  • TIMED_WAIT(计时等待),其进入条件和等待状态类似,但是调用的是存在超时条件的方法,比如wait或join等方法的指定超时版本,如下示例:
    public final native void wait(long timeout) throws InterruptedException;
  • TERMINATED(终止),不管是意外退出还是正常执行结束,线程已经完成使命,终止运行,也有人把这个状态叫作死亡。

在第二次调用start()方法的时候,线程可能处于终止或者其它(非NEW)状态,但是不论如何,都是不可以再次启动的。

知识扩展

1、Java中的线程

从操作系统的角度,可以简单认为,线程是系统调度的最小单元。一个进程可以包含多个线程,作为任务的真正运作者,有自己的栈(Stack)、寄存器(Register)、本地存储(Thread Local)等,但是会和进程内其它线程共享文件描述符、虚拟地址空间等。

在具体实现中,线程还分为内核线程、用户线程。Java的线程实现其实是与虚拟机相关的。对于我们最熟悉的Sun/Oracle JDK,其线程也经历了一个演进过程。基本上在Java 1.2之后,JDK已经抛弃了所谓的Green Thread,也就是用户调用的线程。现在的模型是一对一映射到操作系统内核线程。

总体上来说,Java语言得益于精细粒度的线程和相关的并发操作,其构建高扩展性的大型应用的能力已经毋庸置疑。但是其复杂性也提高了并发编程的门槛,近几年的Go语言等提供了协程(coroutine),大大提高了构建并发应用的效率。与此同时,Java也在Loom项目中,孕育新的类似轻量级用户线程(Fiber)等机制,也许在不久的将来就可以在新版JDK中使用到它。

2、使用Runnable

虽然可以通过直接扩展Thread类并实例化它的方式创建线程,但是我们建议使用Runnable。

Runnable task = () -> {System.out.println("Hello World!");};
Thread myThread = new Thread(task);
myThread.start();
myThread.join();

Runnable的好处是,不会受Java不支持多重继承的限制,当我们需要重复执行相应逻辑时优点明显。而且,也能更好地与现代并发库中的Executor之类框架结合使用。比如将上面的start和join的逻辑完全写成下面的结构:

Future futurn = Executors.newFixedThreadPool(1)
  .submit(task)
  .get();

这样我们就不用操心线程的创建和管理,也能利用Future等机制更好地处理执行结果。线程生命周期通常和业务之间没有本质联系,混淆实现需求和业务需求,就会降低开发的效率。

3、影响线程状态的因素

这里列出了导致线程状态发生改变的操作:

  • 来自线程自身的方法,除了start,还有多个join方法,等待线程结束;yield是告诉调度器,主动让出CPU;另外,就是一些已经被标记为过时的resume、stop、suspend之类。从Java 9开始,destroy/stop方法将被直接移除。
  • 基类Object提供了一些基础的wait/notify/notifyAll方法。如果我们持有某个对象的Monitor锁,调用wait会让当前线程处于等待状态,直到其它线程notify或者notifyAll。所以,本质上是提供了Monitor的获取和释放的能力,是基本的线程间通讯方式。
  • 并发类库中的工具,比如CountDownLatch.await()会让当前线程进入等待状态,直到latch被计数为0,这可以看作是线程间通信的Signal。

以下是线程状态变化图:

4、慎用ThreadLocal

ThreadLocal是Java提供的一种保存线程私有信息的机制,因为其在整个线程生命周期内有效,所以可以方便地在一个线程关联的不同业务模块之间传递信息,比如事务ID、Cookie等上下文相关信息。

它的实现结构可以参考源码,数据存储于线程相关的ThreadLocalMap,其内部条目是弱引用:

static class ThreadLocalMap {
  static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;
    Entry(ThreadLocal<?> k, Object v) {
      super(k);
      value = v;
    }
  }
}

当Key为null时,该条目就变成“废弃条目”,相关“value”的回收,往往依赖于几个关键点,即set、remove、rehash。

通常弱引用都会和引用队列配合清理机制使用,但是ThreadLocal是个例外,它并没有这么做。这意味着,废弃项目的回收依赖于显式地触发,否则就要等待线程结束,进而回收相应ThreadLocalMap!这就是很多OOM的来源。所以通常都会建议,应用一定要自己负责remove,并且不要和线程池配合,因为worker线程往往是不会退出的。

【完】

<think>好的,我现在要回答用户关于Java线程两次调用start方法的问题。首先,我需要回顾一下相关的知识,确保自己理解正确。根据用户提供的引用内容,特别是引用[1]到[5],线程的状态转换是关键。 首先,线程的生命周期。线程在创建后处于NEW状态,当调用start()方法后,进入RUNNABLE状态。如果线程已经执行过start(),再次调用start()时,系统会检查线程的状态。如果线程已经不在NEW状态,就会抛出IllegalThreadStateException异常。这是Java设计上的限制,确保一个线程只能启动一次。 接下来,我需要确认直接调用run()方法和通过start()启动的区别。直接调用run()只是在当前线程中执行,而不会创建新线程。但用户的问题是关于两次调用start(),所以重点应放在多次调用start()的后果上。 然后,考虑用户提供的引用内容。例如,引用[2]提到线程的生命周期,引用[5]指出再次调用start()会导致异常,因为线程状态已经不是NEW了。需要将这些信息整合起来,说明为何不允许多次调用,以及可能的异常情况。 另外,要提到线程状态转换图,说明从NEW到RUNNABLE的状态变化,一旦进入其他状态,如TERMINATED,就无法再次启动。这也是为什么第二次调用start()会失败的原因。 可能还需要比较其他语言或框架的处理方式,但根据引用内容,保持专注在Java上即可。最后,总结结果,明确回答两次调用start()会导致异常,并解释原因,引用相关的资料支持。</think>在Java中,同一个线程对象两次调用$start()$方法会抛出$IllegalThreadStateException$异常。以下是具体分析: ### 1. 线程生命周期与状态限制 - **NEW状态**:线程对象通过$new Thread()$创建后处于NEW状态。 - **RUNNABLE状态**:调用$start()$后,线程进入RUNNABLE状态(包含就绪和运行两个子状态)。 - **TERMINATED状态**:线程执行完$run()$方法后进入终止状态。 **关键规则**: $start()$方法只能在**NEW状态**下调用。若线程已启动(状态变为RUNNABLE或后续状态),再次调用$start()$会违反状态转移规则,触发异常[^1][^2][^4]。 ### 2. 异常产生的直接原因 Java源码中$start()$方法会检查线程状态: ```java public synchronized void start() { if (threadStatus != 0) // 0代表NEW状态 throw new IllegalThreadStateException(); // ...启动线程的后续操作 } ``` 若线程状态非NEW(如已启动或终止),直接抛出异常[^3][^5]。 ### 3. 与直接调用run()的区别 调用$run()$只是普通方法调用,不会启动新线程,也不涉及状态变更。而$start()$通过JVM创建新线程并触发状态转移,因此两次调用$start()$会触发异常,但多次调用$run()$不会。 ### 4. 实验验证 ```java public class ThreadDemo { public static void main(String[] args) { Thread thread = new Thread(() -> System.out.println("Running")); thread.start(); thread.start(); // 第二次调用抛出异常 } } ``` 输出: ``` Running Exception in thread "main" java.lang.IllegalThreadStateException ``` ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值