是System.out.println的多线程输出是否交错


74

如果多个线程在不同步的情况下调用System.out.println(String),输出是否可以交错?还是每行的写入都是原子的?该API只字不提同步的,所以这似乎是可能的,或者是由交错缓冲和/或虚拟机存储器模型等防止输出?

编辑:

例如,如果每个线程包含:

System.out.println("ABC");

保证输出是:

ABC
ABC

或者可能是:

AABC
BC

永远是第一位。但是请阅读@John Vint的答案,因为您可能不希望整个控制台都喷出字符串。
parkovski 2012年

4
请记住,即使System.out.println和System.err.println都是同步的,但两者之间却不同步,因此System.err.println可能与System.out.println交​​错,从而使您的控制台可能无法同步。您的期望。
Pacerier,2012年

实际上,尽管其他人告诉您(jdk 1.6),但在IntelliJ Idea和Eclipse中,我经常会收到交错输出(您演示的情况2)。
mucaho 2014年

非常有趣,@ mucaho。您愿意发布程序和成绩单作为答案吗?
艾伦·斯佩特斯

@espertus不幸的是,我无法提取一个小的示例程序来演示它,但是我可以将您链接到一个测试案例,该案例在运行时显示交错的输出。寻找空行,最上面的行肯定会交织在一起。运行JNetRobust.DelayedTest。确保DEBUG在前几行中将标志设置为true。
mucaho 2014年

Answers:


66

由于API文档未提及System.out对象的线程安全性,因此该PrintStream#println(String)方法 也不能假定它是线程安全的

但是,特定JVM的基础实现完全有可能对该println方法使用线程安全函数(例如printfglibc),因此实际上,将按照您的第一个示例保证输出(始终ABC\n如此ABC\n,永远不要散布字符)根据您的第二个示例)。但是请记住,有许多JVM实现,只需要遵循JVM规范,而无需遵循该规范之外的任何约定。

如果您绝对必须确保任何println调用都不会像您描述的那样散布,那么您必须手动强制执行互斥,例如:

public void safePrintln(String s) {
  synchronized (System.out) {
    System.out.println(s);
  }
}

当然,该示例仅是示例,而不应视为“解决方案”。还有许多其他因素需要考虑。例如,safePrintln(...)仅当所有代码都使用该方法并且没有System.out.println(...)直接调用任何方法时,以上方法才是安全的。


1
Dunno谈到了downvote,但似乎print的标准实现在已包装在同步块中的PrintStream上调用了write方法。因此,不可能通过混入字符来打印出乱码,只是影响字符串打印的顺序。
Gerrit Brink13年

4
@Grep:当然,我只是对记录在案的接口(不保证同步)与常见的实现(可能是同步的)之间的区别而之以鼻。
maerics

6
@Makoto当您说“ println的实现”时,您是在谈论单个JVM的实现,而不是所有JVM的实现。仅仅因为一个JVM选择使该方法线程安全并不意味着所有JVM都这样做-规范并不要求这样做。
maerics 2014年

8
safePrintln并不比简单的打印语句更安全。仅仅因为要在特定对象上进行同步,所以没有任何安全性。仅当所有访问资源的代码在同一个对象上同步时,您才是线程安全的。但是,假设所有调用print方法的代码都在System.out实例上同步,则可以使您回到开始的位置。此外,由于System.out两次读取变量,您的代码也被破坏了。如果有人System.setOut(…)在这两个读取之间调用,您的同步就会中断。
霍尔格2015年

3
@ testerjoe2:这是一种可能的比赛条件。另一点是,另一个线程可以调用System.out.println(s); 而无需 调用synchronized (System.out),因此,仅当所有线程都遵守约定使用该方法(或synchronized自行执行)而不是System.out直接访问并且没有人在调用时,此方法才有效setOut。这就是为什么同步通常与封装结合在一起,而不允许直接访问资源的原因。
Holger

21

OpenJDK源代码回答了您的问题:

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

参考:http : //hg.openjdk.java.net/jdk6/jdk6/jdk/file/39e8fe7a0af1/src/share/classes/java/io/PrintStream.java


6
谢谢,尽管我想知道接口是否保证同步,但不是当前的实现(可以替换)是否提供了同步。
艾伦·斯佩特斯

2
然后声明了println()而不带有“ synchronized”关键字,这清楚地表明它不能保证同步。该实现进一步证明了这一点。
twimo

3
我之所以投票是因为您从具体的实现中推断出了规范(即其他实现的行为),即使在提醒您您不能这样做之后也是如此。您的实现实际上证明,在声明中不需要sync关键字即可实现同步保证。因此,您声称的所有内容都完全违背了逻辑和您自己的示例。
2013年

11

只要您不更改OutputStreamVia,System.setOut它都是线程安全的。

尽管它是线程安全的,但您可以写很多线程System.out,以便

Thread-1
  System.out.println("A");
  System.out.println("B");
  System.out.println("C");
Thread-2
  System.out.println("1");
  System.out.println("2");
  System.out.println("3");

可以阅读

1
2
A
3
B
C

在其他组合中。

因此,回答您的问题:

当您写入时System.out-它获取了OutputStream实例的锁-然后它将写入缓冲区并立即刷新。

释放锁定后, OutputStream刷新并写入。在这种情况下,您不会像那样连接不同的字符串1A 2B

编辑以回答您的编辑:

不会发生这种情况System.out.println。由于PrintStream同步了整个功能,它将填充缓冲区,然后自动进行刷新。现在,任何新线程都会有一个新的缓冲区可以使用。


2
您能否阐明您如何知道在OutputStream上获取了锁?
Ellen Spertus

4
@JohnVint:这里不是要打架,但是如果没有记录,那么您可以做的最好的事情就是说,某些特定的JVM实现实际上是线程安全的。通常,您可能是对的,但不能保证每个JLS或Java API在每个兼容的JVM上都具有这种行为。
maerics'2

4
而且,只有真正看过源代码并且看到它确实正确地进行了同步,您才能完全放心地说。
斯蒂芬·C

2
我想知道的原因是,我是一名教授Java同步的教授,并希望我的学生了解如果他们未能明确使用同步机制,该怎么做和不会出错。我希望能够为他们提供我所声称的任何参考,而不仅仅是在线有人断言。(我不让我的学生引用Wikipedia。)我的意思是不尊重@JohnVint。对于JamesGosling,GuySteele或JoshBloch之外的任何人,我都会做出同样的反应。(我认为约翰·温特(John Vint)可以在我不认识他时不认识我,因为他不认识我是她而不是他。):-)
Ellen Spertus

2
@espertus我对“他”的评论表示歉意!我认为,数学技术在文档方面提出了一个很好的观点。的确,基础实现可以更改并且不会违反合同。如果Oracle决定这样做,那将以失去所有Java开发人员为代价:)话虽如此,如果您要运行程序,则不会看到任何交错的字符串。如果您想确保完整的线程安全并将其记录下来,则可以扩展PrintStream并使用synchronized
John Vint

2

为了澄清起见,假设您有两个线程,一个线程打印"ABC",另一个线程打印"DEF"。您将永远不会得到像这样的输出:ADBECF,但是您可能会得到

ABC
DEF 

要么

DEF
ABC

那只会在print而不是println上发生。Println将自动进行打印,然后写入新行。因此,您将拥有ABC高于DEFDEF高于ABC
John Vint

@parkovski,感谢您的回答,但是您能否解释一下为什么知道输出不会被交错?
Ellen Spertus
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.