“线程因故障而终止”是什么意思?

6
ExecutorService的javadoc有时会提到线程由于“失败”而终止的情况。然而,这种失败指的是什么类型并不清楚。 例如,单线程执行器文档表示: 如果该单个线程在关闭之前由于执行过程中的故障而终止,则如果需要执行后续任务,将会有一个新线程取代它。 我原以为这种情况可能发生在异常或者RuntimeException的情况下,但事实似乎并非如此。运行以下代码似乎会给出相同的线程名称和线程ID。
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> {
  System.out.println("Hello from " + Thread.currentThread().getName()+ " " + Thread.currentThread().getId());
  throw new NullPointerException("Test");
});

executor.submit(() -> {
  System.out.println("Hello 2 from " + Thread.currentThread().getName() + " " + Thread.currentThread().getId());
});
这段代码的输出结果是:
Hello from pool-1-thread-1 12
Hello 2 from pool-1-thread-1 12

似乎即使在NullPointerException的情况下,同一线程也被重复使用。

Javadoc所指的“失败”是什么类型的?


这更像是猜测而非知识,“failure”让我想到的是与软件无关的事情。也许他们指的是硬件故障? - Turing85
嗯...硬件相关的故障听起来太过激了...如果没发生任何事情,为什么会启动另一个线程呢?我想操作系统相关的线程终止可能是更有可能的情况吧。 - jbx
2个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
3
这是一个有趣的问题。根据 ThreadPoolExecutor 中的代码,当传递一个 Runnable 对象到 execute() 方法时,该线程将被丢弃。 当你调用 submit() 方法时,执行器会为调用/可运行对象创建一个类型为 FutureTask 的包装器。 FutureTask.run() 有一些逻辑来捕获异常并存储它们 (所以,之后你可以从 Future 上查询这个异常)。在这种情况下,异常没有传达给 ThreadPool,因此线程不会被丢弃。

那么是什么原因会导致线程“失败”,使ExecutorService检测到其失败并生成一个新的线程? - jbx
2
当调用submit()时,由于包装器的缘故,不会发生这种情况。如果调用execute()并且线程抛出异常,则线程执行器将生成一个新线程。为了测试这一点,可以取出您的代码,并将submit()替换为execute(),然后您会看到执行程序创建了一个新线程。 - Augusto

0

Augusto 是正确的。当作为参数传递给 execute() 方法时,Runnable 任务在遇到异常后应该丢弃线程。

我在这篇 文章Future Task 源代码 中找到了有关 Future 任务吞噬异常的具体证据。

**Inside FutureTask$Sync**

void innerRun() {
        if (!compareAndSetState(READY, RUNNING))
            return;

      runner = Thread.currentThread();
        if (getState() == RUNNING) { // recheck after setting thread
            V result;
           try {
                result = callable.call();
            } catch (Throwable ex) {
               setException(ex);
                return;
            }
           set(result);
        } else {
            releaseShared(0); // cancel
        }
   }


   protected void setException(Throwable t) {
       sync.innerSetException(t);
   }

在SE上还有一些与这个主题相关的有趣问题。

从Java ExecutorService中捕获线程异常

在ExecutorService的submit和execute之间进行选择

编辑:

当线程代码中存在未捕获的异常时,线程失败或终止。如果您通过 execute() 而不是 submit() 提交任务,则除非捕获异常,否则不会捕获异常。线程代码中的未捕获异常将导致线程终止或失败,并由Executor创建新的线程

如果您通过 submit()提交任务,则会创建一个 FutureTask ,并且该任务将吞噬代码中未捕获的异常。由于异常被 FutureTask 捕获,因此线程不会被丢弃。


谢谢您的回复。我仍然不清楚他们所指的“失败”是什么,会导致线程被丢弃并启动一个新线程。您指出的代码清楚地显示了线程状态的检查。我更想知道是什么导致线程死亡(如果不是异常,无论如何都会被捕获)。 - jbx
将使用execute()提交的Runnable而不是submit()提交的Future吞掉异常。如果遇到未捕获的异常并导致线程被丢弃,它们将会终止。请将你的代码更改为使用execute()并检查线程ID。 - Ravindra babu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,