System.out.println 在线程级别上是如何工作的?

3

我正在调查一个简单的HelloWorld Java程序所做的系统调用。通过使用简单的strace,我注意到没有write调用,这让我感到怀疑:

...
mprotect(0x7f0bcd852000, 4096, PROT_READ) = 0
mprotect(0x7f0bce915000, 790528, PROT_READ) = 0
getpid()                                = 27931
munmap(0x7f0bcf6ac000, 174284)          = 0
getpid()                                = 27931
mmap(NULL, 1052672, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7f0bcf5d6000
clone(child_stack=0x7f0bcf6d5fb0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tidptr=0x7f0bcf6d69d0, tls=0x7f0bcf6d6700, child_tidptr=0x7f0bcf6d69d0) = 27932
futex(0x7f0bcf6d69d0, FUTEX_WAIT, 27932, NULLHello, World
) = 0
munmap(0x7f0bbfa6c000, 140063364)       = 0
close(3)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++

所以,如您所见,使用普通的strace调用只会发出futex调用,该调用正在等待字符串地址。
因此,我使用-f参数运行strace,以查看所有线程:
[pid 28249] pread64(3, "\312\376\272\276\0\0\0009\0\330\n\0\2\0\3\7\0\4\f\0\5\0\6\1\0\23java/n"..., 6104, 4355323) = 6104
[pid 28249] pread64(3, "\312\376\272\276\0\0\0009\0\306\n\0\2\0\3\7\0\4\f\0\5\0\6\1\0\20java/l"..., 4455, 3263638) = 4455
[pid 28249] write(1, "Hello, World\n", 13Hello, World
) = 13
[pid 28249] pread64(3, "\312\376\272\276\0\0\0009\0(\n\0\2\0\3\7\0\4\f\0\5\0\6\1\0\25java/l"..., 999, 4425942) = 999
[pid 28249] mmap(0x7f9d35ae2000, 16384, PROT_NONE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = 0x7f9d35ae2000
[pid 28249] mprotect(0x7f9d2c2e7000, 8192, PROT_READ|PROT_WRITE) = 0

这样,我可以看到write调用以及更多的futex调用。

我的问题是,在调用 System.out.println 时,线程级别到底发生了什么? Java是否会为打印创建专用线程,然后将其与主线程同步?此外,它们用于同步的东西最终会产生futex调用。


这不是一个答案,但可能很有趣:https://luckytoilet.wordpress.com/2010/05/21/how-system-out-println-really-works/ - Peter Bagyinszki
“which is waiting on the string address” - 不完全是这样。您在输出中看到的是,“Hello world”是从另一个线程打印出来的,而原始线程正在等待futex(与字符串完全无关)。 - apangin
当调用System.out.println()时,在线程级别上会发生什么?同步,Java是否会为打印创建一个专用线程,然后与主线程同步?不会。 - user207421
2个回答

8

System.out.println与线程无关(除了PrintStream方法被同步,但在没有竞争情况下这并不重要)。

Java启动器会在一个新线程中创建JVM,这就是为什么任何Java代码(不仅仅是println)都在非原始线程中执行的原因。

在原始线程中运行Java代码曾经引起很多问题,有关详情请参见JDK-6316197


啊,好的,所以当你例如写java HelloWorld时,Java虚拟机会获取将要执行的程序,进行一些初始化(我猜测),然后启动线程来运行我们实际的程序。如果我们在程序中没有手动创建其他线程,那么所有的代码都将在该线程中执行。这样理解正确吗? - danield
@danield 不是的,是启动器在初始化JVM之前就创建了一个新线程。然后JVM执行初始化并在该新线程中运行应用程序。 - apangin

0

Java的System.out是一个PrintStream,其中一个方法是:

public void println(String x) {
    synchronized (this) {
        print(x);
        newLine();
    }
}

在那里,您可以看到同步(就像该类中的所有其他print方法一样)。

这是为了避免如果多个线程写入System.out(或任何其他PrintStream)时出现乱码文本。 至少在完整字符串参数上更容易控制,而不是编写单个字符。这也是可能的,但可能会导致乱码输出。

我不知道其余部分,操作系统相关的I/O如何处理内部工作,可能还有更多事情发生。但我没有看过那个。


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