Установка точки останова в недоступном потоке заставляет его работать - PullRequest
0 голосов
/ 29 декабря 2018

У меня странная проблема с этим кодом:

class Test {
    private static boolean test = false;

    public static void main(String[] args) {
        new Thread(() -> {
            while (true) {
                if (test) {
                    System.out.println("Print when breakpoint here!");
                    test = false;
                }
            }
        }, "Thread1").start();

        new Thread(() -> {
            while (true) {
                System.out.println("Print always");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                test = true;
            }
        }, " Thread2").start();
    }
}

Как я и ожидал, поскольку boolean test не volatile, Thread1 использует значение локального кэша test, а когда Thread2 меняет его наtrue Thread1 ничего не сделает.Но когда я ставлю точку останова на линии System.out.println("Prints when put a breakpoint here!"); Она достигнет там и напечатает строку!Что на самом деле происходит, если поставить точку останова?Вызывает ли программа непосредственное чтение значения переменной из памяти?Или что-то еще происходит?

Ответы [ 3 ]

0 голосов
/ 29 декабря 2018

Как я и ожидал, поскольку логический тест не является изменчивым, Thread1 использует значение локального кэша теста, а когда Thread2 изменяет его на true, Thread1 ничего не будет делать.

Ваше ожидание неверно.

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

Так что то, что вы видите, разрешено JLS.


На практике, когдаагент отладки присоединен к JVM, он обычно JIT компилирует методы на более низком уровне оптимизации ... или выполняет их, используя интерпретатор байт-кода.Это может произойти для методов с установленными в них точками останова, а также при одноступенчатом 1 .Это может привести к другому поведению для кода, который использует общие переменные без надлежащей синхронизации при его отладке.

Это одна из причин того, что проблемы с отладкой, вызванные неадекватной синхронизацией, являются трудными.


Насколько я знаю, точки останова меняют инструкции кода, добавляя специальную ловушку под названием INT 3. Так что же на самом деле происходит?

Вот что происходит при отладке C / C ++.Не указано, как JVM справляется с этим, но типичная JVM имеет другие варианты реализации точек останова ... из-за байт-кодов и компиляции JIT.


Когда я вставляю sleep(1) воператор Thread1 before if также напечатает строку.Есть ли то же самое, что происходит при добавлении сна?

sleep приведет к приостановке текущего потока.На уровне реализации не указано .Тем не менее, вероятно , что механизмы собственных потоков будут сбрасывать любые незавершенные записи (например, записи грязного кэша) для приостановленного потока в память ... как часть процесса выполнения переключения контекста потока.

Точно так же, если вы используете операторы print, типичный стек ввода-вывода имеет внутреннюю синхронизацию, которая может вызывать сброс кеша и т. Д. Это также может изменить поведение кода, который вы пытаетесь отлаживать.

Однако я должен подчеркнуть, что эти поведения не указаны.


1 - Оптимизатору JIT разрешено переупорядочивать назначения при условии, что это не изменяет однопоточное поведение.Но если вы отлаживаете метод и наблюдаете, что значения переменных, последствия переупорядочения видны (для программиста).Де-оптимизация / интерпретация избегает этого.К счастью, современный JVM / агент отладки может сделать это «на лету», как требуется.

0 голосов
/ 29 декабря 2018

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

Когда вы добавляете точки журналирования, точки останова или sleep (1), вы замедляете работу приложенияи занимает больше времени, чтобы достичь порога, при котором код оптимизируется.Это означает, что вы не будете видеть проблему, особенно если это состояние гонки.

0 голосов
/ 29 декабря 2018

Предупреждение: этот ответ основан главным образом на том, как работают отладчики .Net, но я ожидаю аналогичного поведения между двумя средами выполнения.Я ожидаю, что JVM разрешит повторную JIT-обработку для каждого метода во время выполнения, поскольку она уже может заменить метод на HotSpot JIT.

Существуют некоторые статьи и посты о том, какие оптимизации отключены для отладки, например AMD: perf при включенной отладке , Побочные эффекты запуска JVM в режиме отладки , Замедлит ли Java-приложение наличие -Xdebug или только при пошаговом выполнении кода? .Они намекают на то, что, по крайней мере, когда существует код исключения, в отладчике используется существенно другой путь к коду, который может иметь способ реализации точек останова.


Многие отладчики отключают оптимизацию (время компиляции, есливы разрешаете перекомпилировать код и время JIT, если вы отлаживаете существующий код), когда вы отлаживаете код.В мире .Net влияние является глобальным - когда подключен отладчик, он может переключать все будущие JIT-компиляции на неоптимизированный путь, я ожидаю, что Java / JVM будет поддерживать более детальный контроль, чтобы позволить неоптимизировать только те методы, которые, возможно, потребуется остановить в отладчике.Это сделано для того, чтобы вы могли четко видеть все значения всех переменных.В противном случае половина информации, иногда включающей вызовы метода и локальные переменные / переменные-члены, недоступна.

«использует значение локального кэша теста» - это оптимизация (вероятно, во время JIT), поэтому при запуске отладки кода (иливключите какой-то пошаговый переход с точками останова), он будет отключать оптимизацию и читать из памяти каждый раз, по существу делая переменную близкой к volatile (все еще не обязательно вести себя таким образом все время, но близко).

В зависимости от используемого отладчика вы можете отключить такое поведение (но отладка будет намного сложнее).

...