Ist die Multithread-Ausgabe von System.out.println verschachtelt?

74

Wenn mehrere Threads System.out.println (String) ohne Synchronisation aufrufen, kann die Ausgabe dann verschachtelt werden? Oder ist das Schreiben jeder Zeile atomar? Die API erwähnt die Synchronisation nicht, so dass dies möglich erscheint, oder wird eine verschachtelte Ausgabe durch Pufferung und / oder das VM-Speichermodell usw. verhindert?

BEARBEITEN:

Zum Beispiel, wenn jeder Thread Folgendes enthält:

System.out.println("ABC");

ist die Ausgabe garantiert:

ABC
ABC

oder könnte es sein:

AABC
BC
Ellen Spertus
quelle
Immer der erste. Aber lesen Sie die Antwort von @John Vint, denn Sie möchten wahrscheinlich nicht, dass Zeichenfolgen über die gesamte Konsole gespuckt werden.
Parkovski
4
Beachten Sie, dass selbst wenn sowohl System.out.println als auch System.err.println synchronisiert sind, diese beiden nicht untereinander synchronisiert sind, sodass System.err.println möglicherweise mit System.out.println verschachtelt ist und Ihnen eine Konsole bietet, die möglicherweise nicht vorhanden ist was du erwartest.
Pacerier
Tatsächlich erhalte ich ziemlich oft verschachtelte Ausgaben (Ihr demonstrierter Fall 2), sowohl in IntelliJ Idea als auch in Eclipse, ungeachtet dessen, was andere Ihnen sagen (jdk 1.6).
Mucaho
Sehr interessant, @mucaho. Wären Sie bereit, ein Programm und ein Transkript als Antwort zu veröffentlichen?
Ellen Spertus
@espertus Leider kann ich kein kleines Beispielprogramm extrahieren, um es zu demonstrieren, aber ich kann Sie mit einem Testfall verknüpfen, der beim Ausführen eine verschachtelte Ausgabe zeigt. Suchen Sie nach leeren Zeilen, die obige Zeile wird mit Sicherheit verschachtelt. Führen Sie JNetRobust.DelayedTest aus . Stellen Sie sicher, dass das DEBUGFlag in den ersten Zeilen auf true gesetzt ist.
Mucaho

Antworten:

65

Da in der API-Dokumentation weder die Thread-Sicherheit des System.outObjekts noch die PrintStream#println(String)Methode erwähnt wird , können Sie nicht davon ausgehen, dass es thread-sicher ist .

Es ist jedoch durchaus möglich, dass die zugrunde liegende Implementierung einer bestimmten JVM eine thread-sichere Funktion für die printlnMethode verwendet (z. B. printfauf glibc ), sodass in Wirklichkeit die Ausgabe gemäß Ihrem ersten Beispiel garantiert wird (immer ABC\ndann ABC\n, niemals eingestreute Zeichen) gemäß Ihrem zweiten Beispiel). Beachten Sie jedoch, dass es viele JVM-Implementierungen gibt und diese nur die JVM-Spezifikation einhalten müssen, keine Konventionen außerhalb dieser Spezifikation.

Wenn Sie unbedingt sicherstellen müssen, dass sich keine Druckanrufe wie beschrieben einmischen, müssen Sie den gegenseitigen Ausschluss manuell erzwingen, zum Beispiel:

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

Natürlich ist dieses Beispiel nur eine Illustration und sollte nicht als "Lösung" verstanden werden. Es gibt viele andere Faktoren zu berücksichtigen. Zum Beispiel ist die safePrintln(...)obige Methode nur sicher, wenn der gesamte Code diese Methode verwendet und nichts System.out.println(...)direkt aufruft .

Maerics
quelle
1
Keine Ahnung über das Downvote, aber es scheint, dass die Standardimplementierung für print usw. die Schreibmethode für den PrintStream aufruft, die bereits in einen synchronisierten Block eingeschlossen ist. Daher wäre es nicht möglich, Jibberish durch Verwechseln der Zeichen im Out auszudrucken. Nur die Reihenfolge, in der die Zeichenfolgen gedruckt werden, kann betroffen sein.
Gerrit Brink
3
@Grep: Sicher, ich bin nur pedantisch in Bezug auf den Unterschied zwischen der dokumentierten Schnittstelle (die keine Synchronisation verspricht) und allgemeinen Implementierungen (die wahrscheinlich synchronisiert sind).
Maerics
6
@Makoto Wenn Sie "die Implementierung von println" sagen, sprechen Sie von der Implementierung einer einzelnen JVM, nicht aller JVMs. Nur weil eine JVM diesen Methoden-Thread sicher gemacht hat, bedeutet dies nicht, dass alle JVMs dies tun - die Spezifikation erfordert dies nicht.
Maerics
8
Das safePrintlnist nicht sicherer als eine einfache Druckanweisung. Nur weil Sie mit einem bestimmten Objekt synchronisieren, erhalten Sie keine Sicherheit. Sie sind nur threadsicher, wenn der gesamte Code, der auf die Ressource zugreift, auf demselben Objekt synchronisiert wird. Wenn Sie jedoch davon ausgehen, dass alle Code-Aufruf-Druckmethoden auf der System.outInstanz synchronisiert werden, kehren Sie zu Ihrem Ausgangspunkt zurück. Außerdem ist Ihr Code fehlerhaft, da er die System.outVariable zweimal liest . Wenn jemand System.setOut(…)direkt zwischen diesen beiden Lesevorgängen anruft , wird die Synchronisierung unterbrochen.
Holger
3
@ testerjoe2: Das ist eine mögliche Rennbedingung. Der andere Punkt ist, dass ein anderer Thread nur System.out.println(s); ohne aufrufen synchronized (System.out)kann. Diese Methode funktioniert also nur, wenn alle Threads die Konvention einhalten, diese Methode zu verwenden (oder synchronizedalleine ausführen), anstatt System.outdirekt darauf zuzugreifen, und niemand anruft setOut. Aus diesem Grund wird die Synchronisierung normalerweise mit der Kapselung kombiniert, sodass kein direkter Zugriff auf die Ressource möglich ist.
Holger
20

Der OpenJDK-Quellcode beantwortet Ihre Frage:

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

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

Twimo
quelle
5
Vielen Dank, obwohl ich wissen wollte, ob die Schnittstelle die Synchronisation garantiert, und nicht, ob aktuelle Implementierungen (die möglicherweise ersetzt werden) eine Synchronisation ermöglichen.
Ellen Spertus
1
Dann hat die Deklaration von println () ohne das Schlüsselwort "synchronized" deutlich gemacht, dass die Synchronisation nicht garantiert wird. Die Implementierung beweist es weiter.
Twimo
3
Ich habe abgestimmt, weil Sie die Spezifikation (dh das Verhalten anderer Implementierungen) aus der konkreten Implementierung ableiten, auch nachdem Sie daran erinnert wurden, dass Sie dies nicht tun können. Ihre Implementierung beweist tatsächlich, dass das Schlüsselwort sync in der Deklaration nicht erforderlich ist, um die Synchronisationsgarantie zu erreichen. Alles, was Sie behaupten, ist völlig gegen die Logik und Ihr eigenes Beispiel.
Val
11

Solange Sie die OutputStreamVia nicht ändern System.setOut, ist sie threadsicher.

Obwohl es threadsicher ist, können Sie viele Threads so schreiben lassen, System.outdass

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");

kann lesen

1
2
A
3
B
C

unter anderen Kombinationen.

Um Ihre Frage zu beantworten:

Wenn Sie in schreiben System.out- es erhält eine Sperre für die OutputStreamInstanz -, wird es in den Puffer schreiben und sofort leeren.

Sobald das Schloss OutputStreamfreigegeben ist , wird das geleert und beschrieben. Es würde keine Instanz geben, in der Sie verschiedene Zeichenfolgen wie verbunden hätten 1A 2B.

Bearbeiten, um Ihre Bearbeitung zu beantworten:

Das würde mit nicht passieren System.out.println. Da das PrintStreamdie gesamte Funktion synchronisiert, füllt es den Puffer und spült ihn dann atomar. Jeder neue Thread, der hereinkommt, hat jetzt einen neuen Puffer, mit dem er arbeiten kann.

John Vint
quelle
2
Können Sie klarstellen, woher Sie wissen, dass eine Sperre für OutputStream erworben wurde?
Ellen Spertus
4
@JohnVint: Ich versuche hier nicht, einen Kampf auszusuchen, aber wenn er nicht dokumentiert ist, können Sie am besten sagen, dass eine bestimmte JVM-Implementierung tatsächlich threadsicher ist. Im Allgemeinen haben Sie wahrscheinlich Recht, aber es gibt keine Garantie für dieses Verhalten bei jeder kompatiblen JVM gemäß der JLS- oder Java-API.
Maerics
4
Und das können Sie nur mit absoluter Sicherheit sagen, wenn Sie sich den Quellcode tatsächlich angesehen und festgestellt haben, dass er tatsächlich ordnungsgemäß synchronisiert wird.
Stephen C
2
Mein Grund zu wissen ist, dass ich Professor für Java-Synchronisation bin und möchte, dass meine Schüler verstehen, was schief gehen kann und was nicht, wenn sie keine expliziten Synchronisationsmechanismen verwenden. Ich möchte ihnen eine Referenz für alles geben können, was ich behaupte, nicht nur, dass jemand online dies behauptet hat. (Ich lasse meine Schüler nicht Wikipedia zitieren.) Ich meine keine Respektlosigkeit gegenüber @JohnVint. Ich würde auf jeden gleich reagieren, nicht auf JamesGosling, GuySteele oder JoshBloch. (Ich glaube nicht, dass John Vint mich beleidigen kann, wenn ich ihn nicht kenne, wenn er nicht weiß, dass ich eine sie bin, kein er.) :-)
Ellen Spertus
2
@espertus Ich entschuldige mich für den 'er'-Kommentar! Ich denke, Maerics bringt einen guten Punkt in Bezug auf die Dokumentation vor. Es ist richtig, dass sich die zugrunde liegende Implementierung ändern und nicht gegen den Vertrag verstoßen kann. Wenn Oracle sich dazu entscheidet, würde dies den Verlust aller Java-Entwickler kosten :) Wenn Sie jedoch Ihr Programm ausführen, werden keine verschachtelten Zeichenfolgen angezeigt. Wenn Sie vollständige Thread-Sicherheit gewährleisten und dokumentieren möchten, können Sie PrintStream erweitern und jede Methode mitsynchronized
John Vint
2

Angenommen, Sie haben zwei Threads, einen, der gedruckt wird, "ABC"und einen, der gedruckt wird "DEF". Sie werden niemals eine solche Ausgabe erhalten: ADBECFaber Sie könnten auch keine bekommen

ABC
DEF 

oder

DEF
ABC
Parkovski
quelle
Das würde nur mit print passieren, nicht mit println. Println druckt atomar und schreibt dann eine neue Zeile. Also hätten Sie ABCoben DEFoder DEFobenABC
John Vint
@parkovski, danke für deine Antwort, aber kannst du erklären, warum du weißt, dass die Ausgabe nicht verschachtelt wird?
Ellen Spertus