如果一个线程执行完它的最后一条语句,一旦完成,它是否保证像显式调用 return 一样死掉?

If a thread finishes executing its last statement, as soon as that is done, is it guaranteed to dead as if explicitly calling return?

以这段代码为例,

static volatile boolean isDone = false;
public static void main(String[] args) throws InterruptedException {
    // I know ArrayBlockingQueue cannot take 0 as the constructor arg, but please for the sake of example, let's pretend this was legal
    final ExecutorService exec = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(0));
    exec.execute(() -> {
        isDone = true;
        // does the compiler add a return statement here?
    });
    while (!isDone){
    }
    try {
        exec.execute(() -> {});
    } catch (RejectedExecutionException e) {
        System.out.println("What if this might actually happen in a super bad timing by the scheduler??");
    }
    exec.shutdown();
}

ExecutorService 创建的第一个 Thread 似乎已经死了,但事实真的如此吗?

在 Thread Api https://docs.oracle.com/javase/6/docs/api/java/lang/Thread.html 它说如果 运行() 方法 returns 那么 Thread 已经死了,但我没有明确调用return 我不确定运行时或编译器实际做了什么。

例如,它可能会在 isDone = true 之后和封闭的 } 之前隐式添加一个 return 语句,例如

exec.execute(() -> {
    isDone = true;
    return;
});

但是,在实际达到 return 之前可能会有一些延迟,因为这取决于调度程序,因此如果调度程序,下一个提交给执行程序的 运行nable 可能会被拒绝决定在执行 exec.execute(() -> {}) 之前不 运行 那 return 语句;在 try 块中。

是的,编译器的行为就好像任何方法的末尾都包含 return; 语句。

不,您的代码不正确。

问题是两个线程可以以任何可能的方式交错。正如墨菲所说,可以发生的(最终)发生。

想象一下,如果执行线程运行 isDone = true 语句然后进入休眠状态。主线程醒来并认为执行器已完成,因此它继续启动一个新的执行器。但是第一个还没有完成(不可见的 return 还没有执行(以及后面的 Java 库中的内部代码)。

等待线程完成的正确方法是Thread.join(),如评论中所述,但您不直接使用线程,而是使用任务。等待任务的正确方式是Future.get()。要获得任务的 Future 对象,请将 execute() 更改为 submit()。除了你想要的 submit returns Future 之外,它们之间似乎没有太大区别。 (另请参阅 What is the difference between submit and execute method with ThreadPoolExecutor

public static void main(String[] args) throws InterruptedException {
    final ExecutorService exec = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
    Future<?> future = exec.submit(() -> {
        // Do work
    });

    try {
        future.get(); //blocking call
    } catch (ExecutionException e) {
        e.getCause().printStackTrace(); // The RuntimeException thrown in the lambda.
    }

    try {
        exec.submit(() -> {});
    } catch (RejectedExecutionException e) {
        // Shouldn't happen
    }
    exec.shutdown();
}

[编辑]

而队列长度为 0 的执行器实际上是可能的:

new ThreadPoolExecutor(1, 1, 0L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());

所有方法将结束。无论您是否显式调用 return 语句。

只需在此处查看 oracle 文档。 Returning a Value from a Method

A method returns to the code that invoked it when it completes all the statements in the method, reaches a return statement, or throws an exception (covered later), whichever occurs first.

总之,你可以把线程看成一堆语句。您可以通过 IDE.

中的 DEBUG 函数查看线程的堆栈

当您启动一个进程时(例如 windows 中的 *.exe 等)

  1. OS 会将您的程序文件加载到内存中。
  2. 然后寻找引导(主要方法)。
  3. 然后将main方法PUSH到Main Thread的栈中。 因此,您甚至可以在代码的任何位置调用 main 方法。 不仅通过开机调用。
  4. 如果一个进程中的所有线程都结束了。该过程将退出。 所以,像许多智能 phone 应用程序一样, 将有一个循环线程等待用户交互操作。 因此,即使您不执行任何操作,应用程序也不会退出 对它的操作。

exec.execute(() -> isDone = true);exec.execute(() -> { isDone = true; return; });

之间没有技术差异

但是,这并没有说明 ExecutorService。除此之外,在观察到 isDone 的写入和 return; 语句的执行之后可能会经过任意时间,即使代码完成也不能保证 ExecutorService 已准备好接受一份新工作。

当你调用executesubmit并且工作线程数达到指定的核心线程数时,只有对队列的offer调用成功才会成功.在 SynchronousQueue 的情况下,它只会成功,如果工作线程已经在队列上调用了 take()。从您的代码返回与已经在队列中调用 take() 不同。

当你使用未来来确保你的工作完成时,这甚至不会改变,例如

final ExecutorService exec = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
    new SynchronousQueue<Runnable>() {
        @Override
        public Runnable take() throws InterruptedException {
            System.out.println(".take()");
            return super.take();
        }
    });
Future<?> f = exec.submit(() -> { return; });
f.get();
try {
    exec.execute(() -> {});
} catch (RejectedExecutionException e) {
    System.err.println(
        "What if this might actually happen in a super bad timing by the scheduler??");
}
exec.shutdown();

在我的机器上偶尔会失败。打印语句足以减慢操作,让启动线程有时超车。

Future 没有帮助,当 get() returns 时,您可以确定您的代码已经完成,因为它的调用者 FutureTask确实已经完成了未来,但是,这与已经在队列上调用工作线程 take() 不同,或者更准确地说,已经达到 take() 方法中足以让offer()调用成功

同样的问题将适用于已满的有界队列,如果工作线程的 take() 调用在 offer() 打电话。

当您专门使用 ThreadPoolExecutor 时,您可以获取队列并手动使用 offer,以在执行程序尚未准备就绪时通知您。

但是,一般的解决方案是不使用使您的应用程序依赖于微妙的线程调度或执行计时细节的线程计数或队列容量。或者使用与 ThreadPoolExecutor.AbortPolicy.

不同的 RejectedExecutionHandler