Многопоточный вывод из System.out.println чередуется

Если несколько потоков вызывают System.out.println (String) без синхронизации, можно ли чередовать выходные данные? Или запись каждой строки атомарна? API не упоминает о синхронизации, поэтому это кажется возможным, или чередование вывода предотвращается буферизацией и / или моделью памяти виртуальной машины и т. д.?

РЕДАКТИРОВАТЬ:

Например, если каждый поток содержит:

System.out.println("ABC");

гарантированно будет вывод:

ABC
ABC

или может быть:

AABC
BC

person Ellen Spertus    schedule 27.02.2012    source источник
comment
Всегда первый. Но прочтите ответ @John Vint, потому что вы, вероятно, не хотите, чтобы строки извергались по всей консоли.   -  person parkovski    schedule 27.02.2012
comment
Имейте в виду, что даже если оба System.out.println и System.err.println синхронизированы, эти два не синхронизируются между собой, поэтому System.err.println может чередоваться с System.out.println, давая вам консоль, которая может не быть чего вы ожидаете.   -  person Pacerier    schedule 09.03.2012
comment
На самом деле я довольно часто получаю чередующийся вывод (ваш продемонстрированный случай 2) как в IntelliJ Idea, так и в Eclipse, несмотря на то, что вам говорят другие (jdk 1.6).   -  person mucaho    schedule 22.09.2014
comment
Очень интересно, @mucaho. Не могли бы вы опубликовать программу и стенограмму в качестве ответа?   -  person Ellen Spertus    schedule 23.09.2014
comment
@espertus К сожалению, я не могу извлечь небольшой пример программы, чтобы продемонстрировать это, но я могу связать вас с тестовым примером, который показывает чередующийся вывод при запуске. Ищите пустые строки, строка выше наверняка будет чередоваться. запустите JNetRobust.DelayedTest. Убедитесь, что для флага DEBUG установлено значение true в первых нескольких строках.   -  person mucaho    schedule 23.09.2014
comment
Да, выход смешивается с несколькими потоками   -  person SMUsamaShah    schedule 03.02.2015


Ответы (4)


Поскольку в документации API не упоминается безопасность потоков на System.out и _ 2_ метод нельзя предполагать, что он является потокобезопасным.

Однако вполне возможно, что базовая реализация конкретной JVM использует поточно-ориентированную функцию для метода println (например, _ 4_ в glibc), так что на самом деле вывод будет гарантирован в соответствии с вашим первым примером (всегда ABC\n, затем ABC\n, никогда не перемежающиеся символы в вашем втором примере). Но имейте в виду, что существует множество реализаций JVM, и от них требуется только соблюдение спецификации JVM, а не каких-либо соглашений, выходящих за рамки этой спецификации.

Если вы абсолютно должны гарантировать, что никакие вызовы println не будут перемежаться, как вы описываете, тогда вы должны принудительно применить взаимное исключение вручную, например:

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

Конечно, этот пример является лишь иллюстрацией и не должен восприниматься как «решение»; необходимо учитывать множество других факторов. Например, приведенный выше метод safePrintln(...) безопасен только в том случае, если весь код использует этот метод и ничто не вызывает System.out.println(...) напрямую.

person maerics    schedule 27.02.2012
comment
Не знаю насчет отрицательного голоса, но кажется, что стандартная реализация для печати и т.д. вызывает метод записи в PrintStream, который уже заключен в синхронизированный блок. Таким образом, было бы невозможно распечатать тарабарщину путем смешивания символов на выходе, это может быть затронуто только порядком, в котором печатаются строки. - person Gerrit Brink; 26.09.2013
comment
@Grep: конечно, я просто педантично говорю о разнице между документированным интерфейсом (который не обещает синхронизацию) и обычными реализациями (которые, вероятно, синхронизированы). - person maerics; 23.02.2014
comment
Как объяснить, что реализация println на самом деле здесь то же самое, что вы описываете? Разве это не делает его поточно-ориентированным в том же духе, что и ваше решение? - person Makoto; 28.09.2014
comment
@Makoto, когда вы говорите реализация println, вы имеете в виду реализацию одной JVM, а не всех JVM. Тот факт, что одна JVM решила сделать этот метод потокобезопасным, не означает, что это делают все JVM - спецификация этого не требует. - person maerics; 28.09.2014
comment
safePrintln не безопаснее простого оператора печати. Вы не получаете никакой безопасности только потому, что вы синхронизируете с определенным объектом. Вы являетесь потокобезопасным только тогда, когда весь код, обращающийся к ресурсу, синхронизируется с одним и тем же объектом. Но если предположить, что весь код, вызывающий методы печати, синхронизируется с экземпляром System.out, вы вернетесь туда, откуда вы начали. Кроме того, ваш код не работает, поскольку он дважды читает переменную System.out. Если кто-то вызывает System.setOut(…) прямо между этими двумя чтениями, синхронизация прерывается. - person Holger; 01.10.2015
comment
@Holger: да, мой образец кода, очевидно, является иллюстрацией, а не реальным решением. Я обновлю ответ, чтобы сказать об этом. - person maerics; 01.10.2015
comment
@maerics - укажите некоторые факторы, которые следует учитывать, прежде чем выбирать реализацию потокобезопасного println. - person MasterJoe; 13.07.2017
comment
@Holger - потокобезопасность обеспечивается только тогда, когда ... на одном и том же объекте. Пожалуйста, объясните это. Что здесь за ресурс? - person MasterJoe; 13.07.2017
comment
@ testerjoe2: «ресурс» - это то, к чему вы хотите получить доступ в потокобезопасном режиме, здесь это консоль (или stout). Больше объяснять нечего, synchronized устанавливает взаимное исключение и безопасность потоков между потоками, использующими его с одним и тем же объектом. Вот что он делает. - person Holger; 13.07.2017
comment
@Holger - Здесь новичок. Не является ли приведенный выше код потокобезопасным только потому, что он дважды читает переменную System.out (поэтому возможно, что кто-то может вызвать System.setOut (…) прямо между этими двумя считываниями)? - person MasterJoe; 13.07.2017
comment
@ testerjoe2: Это одно из возможных состояний гонки. Другой момент заключается в том, что другой поток может просто вызвать System.out.println(s); без synchronized (System.out), поэтому этот метод работает, только если все потоки придерживаются соглашения об использовании этого метода (или делают synchronized самостоятельно) вместо доступа к System.out прямо и что никто не звонит setOut. Вот почему синхронизация обычно сочетается с инкапсуляцией, не позволяющей прямой доступ к ресурсу. - person Holger; 13.07.2017
comment
@Holger - Еще раз спасибо! Попытка понять без synchronized (System.out) --- Некоторые JVM могут не реализовывать println, который синхронизируется на System.out. Когда мы используем код ответа, другие потоки могут получить доступ к system.out. Итак, если println не объявлен как синхронизированный метод, вы не можете гарантировать потокобезопасность system.out. Я правильно понял? - person MasterJoe; 14.07.2017

Исходный код 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

person twimo    schedule 06.05.2012
comment
Спасибо, хотя я хотел знать, гарантирует ли интерфейс синхронизацию, а не обеспечивают ли текущие реализации (которые могут быть заменены) синхронизацию. - person Ellen Spertus; 07.05.2012
comment
Затем объявление println () без ключевого слова synchronized дало понять, что оно не гарантирует синхронизацию. Реализация еще раз доказывает это. - person twimo; 07.05.2012
comment
Я проголосовал против, потому что вы делаете вывод о спецификации (то есть о поведении других реализаций) из конкретной реализации даже после того, как вам напомнили, что вы не можете этого сделать. Ваша реализация фактически доказывает, что ключевое слово sync не требуется в объявлении для достижения гарантии синхронизации. Итак, все, что вы утверждаете, полностью противоречит логике и вашему собственному примеру. - person Val; 06.11.2013

Пока вы не меняете 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");

может читать

1
2
A
3
B
C

среди других комбинаций.

Итак, чтобы ответить на ваш вопрос:

Когда вы пишете в System.out - он блокирует экземпляр OutputStream - затем он записывает в буфер и немедленно сбрасывается.

Как только он снимает блокировку, OutputStream сбрасывается и записывается в. Не было бы случая, когда у вас были бы соединены разные строки, например 1A 2B.

Отредактируйте, чтобы ответить на ваше редактирование:

Этого бы не случилось с System.out.println. Поскольку PrintStream синхронизирует всю функцию, он заполняет буфер, а затем очищает его атомарно. Теперь у любого нового входящего потока будет новый буфер для работы.

person John Vint    schedule 27.02.2012
comment
спасибо за ответ, но я думаю, вы неправильно поняли мой вопрос. Я попытался прояснить это. - person Ellen Spertus; 27.02.2012
comment
Да, я ответил, что внизу я мог бы внести ясность. - person John Vint; 27.02.2012
comment
Есть ли где-то документация, которая заставляет вас поверить в то, что на экземпляре OutputStream установлена ​​блокировка? - person maerics; 27.02.2012
comment
Это не задокументировано. Я говорю просто по сути. - person John Vint; 27.02.2012
comment
Не могли бы вы прояснить, откуда вы знаете, что на OutputStream установлена ​​блокировка? - person Ellen Spertus; 27.02.2012
comment
@JohnVint: не пытаюсь вступить в драку здесь, но если это не задокументировано, лучшее, что вы можете сделать, это сказать, что какая-то конкретная реализация JVM на самом деле является потокобезопасной. В целом вы, вероятно, правы, но нет никакой гарантии такого поведения на каждой совместимой JVM в соответствии с JLS или Java API. - person maerics; 27.02.2012
comment
И вы можете сказать это с полной уверенностью, только если вы действительно посмотрели исходный код и увидели, что он действительно синхронизируется правильно. - person Stephen C; 27.02.2012
comment
Я посмотрел источник, он синхронизируется. Есть также документы о том, что PrintStream является потокобезопасным. Если OP не хочет использовать его, потому что он не документирован как потокобезопасный, который находится на нем. Я просто утверждаю, что это потокобезопасный - person John Vint; 27.02.2012
comment
Причина, по которой я хочу знать, заключается в том, что я профессор, преподающий о синхронизации Java, и хочу, чтобы мои студенты понимали, что может и не может пойти не так, если они не смогут явно использовать механизмы синхронизации. Я хотел бы иметь возможность дать им ссылку на все, что я утверждаю, а не только на то, что кто-то в Интернете это подтвердил. (Я не позволяю своим ученикам цитировать Википедию.) Я не имею в виду неуважение к @JohnVint. Я бы отреагировал так же на всех, кроме Джеймса Гослинга, Гая Стила или Джоша Блоха. (Не думаю, что Джон Винт может обидеться на то, что я не знаю его, когда он не знал, что я она, а не он.) :-) - person Ellen Spertus; 28.02.2012
comment
@StephenC, на самом деле, даже взглянуть на исходный код недостаточно. Он должен быть частью API, прежде чем мы сможем считать его истинным для всех реализаций. - person Ellen Spertus; 28.02.2012
comment
@espertus - на самом деле, вы неправильно поняли мой комментарий. Его следует читать в контексте предыдущего комментария; т.е. это неявно характеризует конкретную реализацию JVM. - person Stephen C; 28.02.2012
comment
@espertus Мои извинения за комментарий «он»! Я думаю, что maerics поднимает хороший вопрос относительно документации. Верно, что основная реализация может измениться и не нарушить контракт. Если Oracle решит сделать это, это будет стоить потери всех Java-разработчиков :) При этом, если вы запустите свою программу, вы не увидите никаких чередующихся строк. Если вы хотите обеспечить полную безопасность потоков и задокументировать это, вы можете расширить PrintStream и переопределить каждый метод с помощью synchronized - person John Vint; 28.02.2012
comment
@espertus Нет проблем - это хороший вопрос - я упустил из виду отсутствие спецификации в Java API. - person John Vint; 28.02.2012

Чтобы уточнить, предположим, что у вас есть два потока, один из которых печатает "ABC", а другой - "DEF". Вы никогда не получите такой результат: ADBECF, но вы можете получить и то, и другое.

ABC
DEF 

or

DEF
ABC
person parkovski    schedule 27.02.2012
comment
Это произойдет только с print, а не с println. Println будет атомарно печатать, а затем писать новую строку. Таким образом, у вас будет ABC выше DEF или DEF выше ABC - person John Vint; 27.02.2012
comment
@parkovski, спасибо за ответ, но не могли бы вы объяснить, почему вы знаете, что вывод не будет чередоваться? - person Ellen Spertus; 27.02.2012