java Статический блок и блок инициализации (какой из них загружен первым статическим или инициализационным блоком, потому что выход изменяется в случае err.out) [duplicate]

Интересно, что ни один из ответов на этой странице не упоминает два крайних случая, надеюсь, никто не возражает, если я их добавлю:

Случай с краем # 1: одновременный доступ к словарю

Родовые словари в .NET не являются потокобезопасными, а иногда могут бросать NullReference или даже (чаще) a KeyNotFoundException при попытке получить доступ к ключу из двух параллельных потоков. Исключение в этом случае является довольно ошибочным.

Случай с краем # 2: небезопасный код

Если код NullReferenceException задан кодом unsafe, вы можете посмотреть на переменные указателя , и проверьте их на IntPtr.Zero или что-то в этом роде. Это одно и то же («исключение нулевого указателя»), но в небезопасном коде переменные часто переводятся в типы значений / массивы и т. Д., И вы ударяете головой о стену, задаваясь вопросом, как тип значения может исключение.

(Еще одна причина для небезопасного использования небезопасного кода, если вам это нужно)

43
задан Nick Heiner 10 December 2009 в 20:20
поделиться

6 ответов

Они разные потоки и сбрасываются в разное время.

Если вы положите

System.out.flush();
System.err.flush();

внутри своего цикла, он будет работать как ожидалось.

Чтобы уточнить, выходные потоки кэшируются, поэтому вся запись поступает в этот буфер памяти. После периода молчания они фактически выписываются.

Вы пишете два буфера, затем после периода бездействия оба они покраснели (один за другим).

33
ответ дан Bill K 5 September 2018 в 07:45
поделиться

Если вы используете консоль Eclipse, на работе, похоже, работают два разных явления: один, как описано в @Gemtastic , является обработкой потоков JVM, а другой - способом Eclipse читает эти потоки, как упоминалось @DraganBozanovic . Поскольку я использую Eclipse, элегантный flush() -решение, отправленное @BillK , которое касается только проблемы с JVM, недостаточно.

В итоге я написал вспомогательный класс, называемый EclipseTools со следующим содержимым (и требуемым объявлением пакета и импортом). Это немного взломать, но исправляет обе проблемы:

public class EclipseTools {

    private static List<OutputStream> streams = null;
    private static OutputStream lastStream = null;

    private static class FixedStream extends OutputStream {

        private final OutputStream target;

        public FixedStream(OutputStream originalStream) {
            target = originalStream;
            streams.add(this);
        }

        @Override
        public void write(int b) throws IOException {
            if (lastStream!=this) swap();
            target.write(b);
        }

        @Override
        public void write(byte[] b) throws IOException {
            if (lastStream!=this) swap();
            target.write(b);
        }

        @Override
        public void write(byte[] b, int off, int len) throws IOException {
            if (lastStream!=this) swap();
            target.write(b, off, len);
        }

        private void swap() throws IOException {
            if (lastStream!=null) {
                lastStream.flush();
                try { Thread.sleep(200); } catch (InterruptedException e) {}
            }
            lastStream = this;
        }

        @Override public void close() throws IOException { target.close(); }
        @Override public void flush() throws IOException { target.flush(); }
    }

    /**
     * Inserts a 200ms delay into the System.err or System.out OutputStreams
     * every time the output switches from one to the other. This prevents
     * the Eclipse console from showing the output of the two streams out of
     * order. This function only needs to be called once.
     */
    public static void fixConsole() {
        if (streams!=null) return;
        streams = new ArrayList<OutputStream>();
        System.setErr(new PrintStream(new FixedStream(System.err)));
        System.setOut(new PrintStream(new FixedStream(System.out)));
    }
}

Чтобы использовать, просто вызовите EclipseTools.fixConsole() один раз в начале вашего кода.

В основном это заменяет два потоки System.err и System.out с настраиваемым набором потоков, которые просто пересылают свои данные в исходные потоки, но отслеживают, какой поток был записан последним. Если поток, который записывается для изменений, например, System.err.something(...), за которым следует System.out.something(...), он очищает вывод последнего потока и ждет 200 мс, чтобы дать время консоли Eclipse завершить его печать.

Примечание: 200 мс - это просто приблизительное начальное значение. Если этот код уменьшает, но не устраняет проблему для вас, увеличьте задержку в Thread.sleep от 200 до чего-то большего, пока она не будет работать. В качестве альтернативы, если эта задержка работает, но влияет на производительность вашего кода (если вы чередуете потоки часто), вы можете попытаться уменьшить его постепенно, пока не начнете получать ошибки.

9
ответ дан Community 5 September 2018 в 07:45
поделиться

Это ошибка в Eclipse . Похоже, что Eclipse использует отдельные потоки для чтения содержимого потоков out и err без какой-либо синхронизации.

Если вы скомпилируете класс и выполните его в консоли (с классическим java <main class name>), , порядок будет таким, как ожидалось.

1
ответ дан Dragan Bozanovic 5 September 2018 в 07:45
поделиться

Я использовал поток для вывода вывода System.out и System.err последовательно:

    for(int i = 0; i< 5; i++){
        try {
            Thread.sleep(100);
            System.out.print("OUT");
            Thread.sleep(100);
            System.err.print("ERR");
        }catch (InterruptedException ex){
            System.out.println(ex.getMessage());
        }
    }
0
ответ дан Milan Paudyal 5 September 2018 в 07:45
поделиться

Это вызвано возможностью в JVM и, если вы не делаете взломать, например, тот, который предоставлен Marcus A., на самом деле не так просто работать. .flush() работает в этом случае, но причина этого намного сложнее в работе.

Что здесь происходит?

Когда вы программируете на Java, вы не говоря компьютеру, что делать, вы сообщаете JVM (виртуальная машина Java), что вы хотели бы сделать. И он будет делать это, но более эффективно. Ваш код не является точным подробным инструкциям, в этом случае вам нужен только компилятор, как на C и C ++, JVM берет ваш код в качестве списка спецификаций для того, что он должен оптимизировать, а затем делать. Это то, что происходит здесь. Java видит, что вы нажимаете строки в два разных потока буферов. Самый эффективный способ сделать это - буферизировать все строки, которые вы хотите передать потокам, а затем вывести их. Это происходит по одному потоку в то время, существенно преобразуя ваш код, сделав что-то вроде этого (остерегайтесь: псевдокод) :

for(int i = 0; i < 5; i++) {
    out.add();
    err.add();
}
out.flush();
err.flush();

Поскольку это более эффективно, Вместо этого будет работать JVM. Добавление .flush() в цикле будет сигнализировать JVM о необходимости промывки в каждом цикле, который не может быть улучшен с помощью вышеуказанного метода. Но если вы объясните, как это будет работать, цикл JVM изменит порядок вашего кода, чтобы сделать его последним, потому что это более эффективно.

System.out.println("out");
System.out.flush();
System.err.println("err");
System.err.flush();
System.out.println("out");
System.out.flush();
System.err.println("err");
System.err.flush();

Этот код будет всегда реорганизуются на что-то вроде этого:

System.out.println("out");*
System.err.println("err");*
System.out.println("out");*
System.err.println("err");*
System.out.flush();
System.err.flush();

Поскольку буферизация многих буферов только для их очистки сразу после того, как требуется намного больше времени, чем для буферизации всего кода, который будет буферизироваться, а затем очистить все это в то же время.

Как это решить

В этом случае код и дизайн кода могут вступить в игру; вы вроде бы не решаете этого. Чтобы обойти это, вы должны сделать его более эффективным для буферизации печати / флеша, печати буфера или флеша, чем буфер, а затем сброс. Это, скорее всего, соблазнит вас неудачным дизайном. Если для вас важно, как вывести его упорядоченно, я предлагаю вам попробовать другой подход. For-looping с .flush() - это один из способов взломать его, но вы все еще взламываете функцию JVM, чтобы перенастроить и оптимизировать свой код для вас.

* Я не могу проверить, что буфер, который вы добавили сначала, всегда будет печатать первым, но, скорее всего, будет.

25
ответ дан neuralmer 5 September 2018 в 07:45
поделиться

Два оператора println обрабатываются двумя разными потоками. Результат снова зависит от того, в какой среде вы запускаете код. Например, я выполнил следующий код в IntelliJ и в командной строке по 5 раз.

public class Test {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.print("OUT ");
            System.err.print("ERR ");
        }
    }
}

Это приводит к следующему выводу: Commandline

OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR
OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR
OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR
OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR
OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR OUT ERR

IntelliJ:

ERR ERR ERR ERR ERR ERR ERR ERR ERR ERR OUT OUT OUT OUT OUT OUT OUT OUT OUT OUT 
OUT OUT OUT OUT OUT OUT OUT OUT OUT OUT ERR ERR ERR ERR ERR ERR ERR ERR ERR ERR
ERR ERR ERR ERR ERR ERR ERR ERR ERR ERR OUT OUT OUT OUT OUT OUT OUT OUT OUT OUT 
ERR ERR ERR ERR ERR ERR ERR ERR ERR ERR OUT OUT OUT OUT OUT OUT OUT OUT OUT OUT
OUT OUT OUT OUT OUT OUT OUT OUT OUT OUT ERR ERR ERR ERR ERR ERR ERR ERR ERR ERR 

Я полагаю, что разные среды обрабатывают буферы по-разному. Один из способов увидеть, что эти потоки являются infact обработаны различными потоками, заключается в том, чтобы добавить оператор sleep в цикле. Вы можете попробовать изменить значение, которое вы установили для сна, и посмотреть, что это infact, обрабатываемые разными потоками.

public class Test {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.print("OUT ");
            System.err.print("ERR ");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Выход в этом случае оказался

OUT ERR ERR OUT ERR OUT OUT ERR OUT ERR ERR OUT OUT ERR ERR OUT OUT ERR OUT ERR
OUT ERR ERR OUT ERR OUT OUT ERR OUT ERR ERR OUT OUT ERR ERR OUT OUT ERR OUT ERR
ERR OUT ERR OUT OUT ERR ERR OUT OUT ERR ERR OUT OUT ERR OUT ERR ERR OUT ERR OUT 
ERR OUT OUT ERR ERR OUT OUT ERR ERR OUT ERR OUT OUT ERR ERR OUT ERR OUT OUT ERR
OUT ERR OUT ERR ERR OUT OUT ERR ERR OUT OUT ERR ERR OUT ERR OUT OUT ERR OUT ERR 

Один из способов заставить его распечатать его в том же порядке, будет использовать .flush(), который работал для меня. Но itseems, что не все получают правильные результаты с ним.

Два потока, обработанные двумя двумя разными потоками, вероятно, являются причиной того, что мы иногда видим сообщение ERROR, напечатанное некоторыми библиотеками, которые мы используем, печататься перед некоторыми заявлениями о печати, которые мы должны были видеть в соответствии с порядком исполнения.

1
ответ дан Rakesh 5 September 2018 в 07:45
поделиться
Другие вопросы по тегам:

Похожие вопросы: