非守护线程输出有时会消失,直到使用 join()

Non-daemon threads output sometimes disappear until join() being used

我有一个小测试,只使用 两个线程,如下所示:

import static java.lang.System.out;


@Test
public void testTwoSimpleThreads() {
    doInParallelAsync(() -> {
                out.println("First Ok");
            },
            () -> {
                out.println("Second Ok");
            });
}

private void doInParallelAsync(Runnable first, Runnable second) {
    new Thread(() -> {
        first.run();
    }).start();

    new Thread(() -> {
        second.run();
    }).start();
}

有时输出包括其中之一,然后我介绍join()它们开始后。

到目前为止我遇到了两种不同的输出:

一个

First Ok

两个

First Ok
Second Ok

我知道 println()synchronized 而且我知道 main 线程创建的线程默认是用户线程并且用户线程将在完成之前不会退出

虽然我使用的是@Test,但我测试过它们是预期的非守护进程

non-daemon

is a daemon thread if and only if the creating thread is a daemon

The Java Virtual Machine continues to execute threads until either of the following occurs:

  1. The exit method of class Runtime has been called and the security manager has permitted the exit operation to take place.

  2. All threads that are not daemon threads have died, either by returning from the call to the run method or by throwing an exception that propagates beyond the run method.

已更新

其实我知道答案指出的是什么,我想知道为什么输出消失了?

我要问的问题:

  1. 管道被主线程关闭了?

  2. 为什么?是因为每个管道都独立绑定到一个线程吗?没有共享? (在最后一个线程完成之前管道不会关闭,就像 引用计数 - 文件在没有人使用它之前不会被删除)

  3. 最后一个问题:是JVM控制还是依赖OS?

似乎 管道 被我提到的 @Test 关闭了。谢谢@meriton 指出来。

结论

当我在 普通 main() 中尝试此方法并在 start() 之后直接调用 System.exit(0); 时,输出是与使用 @Test.

时完全相同

使用@Test (Junit) 时,不会等待线程。更多详情,请查看JUnit test not executing all threads created within the test and JUnit terminates child threads.

这是@Alex Lockwood 的解释,我个人比较喜欢:

JUnit is a unit testing framework... as with the Android framework, it has a main thread from which it will call user-defined unit test methods. When a unit test returns, JUnit immediately invokes the next unit test method (or exits completely if there are no more unit test methods to call). JUnit doesn't know anything about the background threads you create/start, so you can't assume that it will sit around and wait for them to finish.

那是因为您在不同的线程上启动了它们,并且 main thread 不会等待 child threads 完成并将在最后一行后立即关闭其执行。

因此,到那时,如果执行线程 1,输出将是:

First Ok

如果线程 2 被执行:

Second Ok

如果幸运的是两人都被处决了那么

First Ok 
Second Ok

Second Ok 
First Ok

通过提供 join,您要求 main thread 等待 child thread 完成,从而完成两个输出。

-- 已编辑 --

这并不意味着子线程已终止,它们仍然完成了执行,但您可能无法看到结果,因为到那时外流可能已关闭或释放

如果你在 Jshell 上执行相同的命令,你总是会得到第二个输出,但有时它会作为另一个命令出现,因为一旦主线程完成,Jshell 就会移动到下一个命令行:

测试 运行 是否可以在所有测试 运行 之后调用 System.exit()?如果将测试转换为普通的 main() 方法,是否可以重现部分输出?

(我自己尝试过,但是当 运行将其作为 Eclipse 中的 JUnit 测试时无法重现部分输出)

... 不,System.out 是一个静态字段,因此由所有线程共享。退出主线程不会关闭流。