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

Ell*_*tus 67 java multithreading synchronization printstream

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

编辑:

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

System.out.println("ABC");
Run Code Online (Sandbox Code Playgroud)

输出保证是:

ABC
ABC
Run Code Online (Sandbox Code Playgroud)

或者它可能是:

AABC
BC
Run Code Online (Sandbox Code Playgroud)

mae*_*ics 58

由于API文档,使上没有提及线程安全的System.out对象也不该PrintStream#println(String)方法 不能认为它是线程安全的.

但是,特定JVM的底层实现完全有可能使用该println方法的线程安全函数(例如,printf在glibc上),因此实际上,每个第一个示例都会保证输出(总是ABC\n那么ABC\n,从不散布的字符)按照你的第二个例子).但请记住,有许多JVM实现,并且只需遵守JVM规范,而不是该规范之外的任何约定.

如果您绝对必须确保没有println调用会在您描述时散布,那么您必须手动强制执行互斥,例如:

public void safePrintln(String s) {
  synchronized (System.out) {
    System.out.println(s);
  }
}
Run Code Online (Sandbox Code Playgroud)

当然,这个例子只是一个例子,不应被视为"解决方案"; 还有许多其他因素需要考虑.例如,safePrintln(...)如果所有代码都使用该方法并且没有System.out.println(...)直接调用,则上述方法是安全的.

  • `safePrintln`并不比简单的print语句更安全.仅仅因为*你*正在同步某个特定对象,你就没有任何安全感.当访问资源的*all*代码在同一对象上同步时,您只是线程安全的.但假设调用打印方法的所有代码都在`System.out`实例上进行同步,则会将您带回到已启动的位置.此外,您的代码被破坏,因为它读取`System.out`变量两次.如果有人在这两次读取之间调用`System.setOut(...)`,则会导致同步中断. (6认同)
  • @Makoto当你说"println的*implementation*"时,你正在讨论*单个*JVM的实现,而不是所有的JVM.仅仅因为一个JVM选择使该方法线程安全,它并不意味着*所有*JVM都做 - 规范不要求它. (5认同)
  • @Grep:当然,我只是对于记录的*接口*(没有承诺同步)与普通*实现*(可能是同步的)之间的差异而迂腐. (3认同)
  • @ testerjoe2:这是一种可能的竞争条件.另一点是另一个线程可以只调用`System.out.println(s);`*without*`synchronized(System.out)`,所以这个方法只有在所有线程都遵守约定才能使用这个方法时才有效(或者自己做`synchronized`)而不是直接访问`System.out`而没有人调用`setOut`.这就是为什么同步通常与封装相结合,不允许直接访问资源的原因. (3认同)

twi*_*imo 20

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

public void println(String x) {
    synchronized (this) {
        print(x);
        newLine();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • 谢谢,虽然我想知道接口是否保证同步,而不是当前实现(可能被替换)是否提供了同步. (4认同)
  • 然后没有“synchronized”关键字的 println() 声明已经清楚地表明它不保证同步。实施进一步证明了这一点。 (2认同)
  • 我已经downvoted,因为你从具体的实现中推断出规范(即其他实现的行为),即使它被提醒你,你不能这样做.您的实现实际上证明在声明中不需要sync关键字来实现同步保证.所以,你声称的一切都完全违背逻辑和你自己的例子. (2认同)

Joh*_*int 10

只要您不更改OutputStream通道,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");
Run Code Online (Sandbox Code Playgroud)

可以阅读

1
2
A
3
B
C
Run Code Online (Sandbox Code Playgroud)

其他组合.

所以回答你的问题:

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

一旦它释放锁定,OutputStream就会刷新并写入.没有一个实例,你会有不同的字符串加入像1A 2B.

编辑以回答您的修改:

这不会发生System.out.println.由于PrintStream同步整个函数,它将填充缓冲区,然后以原子方式对其进行刷新.任何进入的新线程现在都有一个新的缓冲区可供使用.

  • @JohnVint:不要试图在这里挑战,但如果没有记录,那么你能做的最好就是说某些特定的JVM实现实际上是线程安全的.通常,您可能是正确的,但根据JLS或Java API,并不能保证每个兼容的JVM都有这种行为. (4认同)
  • 如果您真正查看了源代码,并且看到它确实正确地进行了同步,那么您只能完全放心地说. (4认同)
  • 你能澄清一下你是如何知道在OutputStream上获得锁的吗? (2认同)
  • 我想知道的原因是我是一名教授Java同步的教授,并希望我的学生了解如果他们未能明确使用同步机制会出现什么问题,哪些问题可能出错.我希望能够为他们所声称的任何内容提供参考,而不仅仅是有人在线声称它.(我不会让我的学生引用维基百科.)我的意思是对@JohnVint没有任何不尊重.我对JamesGosling,GuySteele或JoshBloch的任何人都有同样的反应.(我不认为John Vint可以冒犯我不认识他的时候他不知道我是她,而不是他.):-) (2认同)
  • @espertus我对'他'的评论表示歉意!我认为maerics在文档方面提出了一个很好的观点.确实,底层实施可以改变而不是违反合同.如果Oracle决定这样做,那将以失去所有Java开发人员为代价:)尽管如此,如果你要运行程序,你将看不到任何交错的字符串.如果要确保完整的线程安全并记录它,可以扩展PrintStream并使用`synchronized`覆盖每个方法 (2认同)