如果多个线程在不同步的情况下调用System.out.println(String),输出是否可以交错?还是每行的写入都是原子的?该API只字不提同步的,所以这似乎是可能的,或者是由交错缓冲和/或虚拟机存储器模型等防止输出?
编辑:
例如,如果每个线程包含:
System.out.println("ABC");
保证输出是:
ABC
ABC
或者可能是:
AABC
BC
如果多个线程在不同步的情况下调用System.out.println(String),输出是否可以交错?还是每行的写入都是原子的?该API只字不提同步的,所以这似乎是可能的,或者是由交错缓冲和/或虚拟机存储器模型等防止输出?
编辑:
例如,如果每个线程包含:
System.out.println("ABC");
保证输出是:
ABC
ABC
或者可能是:
AABC
BC
DEBUG
在前几行中将标志设置为true。
Answers:
由于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);
}
}
当然,该示例仅是示例,而不应视为“解决方案”。还有许多其他因素需要考虑。例如,safePrintln(...)
仅当所有代码都使用该方法并且没有System.out.println(...)
直接调用任何方法时,以上方法才是安全的。
safePrintln
并不比简单的打印语句更安全。仅仅因为您要在特定对象上进行同步,所以没有任何安全性。仅当所有访问资源的代码在同一个对象上同步时,您才是线程安全的。但是,假设所有调用print方法的代码都在System.out
实例上同步,则可以使您回到开始的位置。此外,由于System.out
两次读取变量,您的代码也被破坏了。如果有人System.setOut(…)
在这两个读取之间调用,您的同步就会中断。
System.out.println(s);
而无需 调用synchronized (System.out)
,因此,仅当所有线程都遵守约定使用该方法(或synchronized
自行执行)而不是System.out
直接访问并且没有人在调用时,此方法才有效setOut
。这就是为什么同步通常与封装结合在一起,而不允许直接访问资源的原因。
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
只要您不更改OutputStream
Via,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
同步了整个功能,它将填充缓冲区,然后自动进行刷新。现在,任何新线程都会有一个新的缓冲区可以使用。
synchronized
为了澄清起见,假设您有两个线程,一个线程打印"ABC"
,另一个线程打印"DEF"
。您将永远不会得到像这样的输出:ADBECF
,但是您可能会得到
ABC
DEF
要么
DEF
ABC
ABC
高于DEF
或DEF
高于ABC