Подтвердить что ты не робот

Почему "синхронизирован" (новый объект()) {} `no-op?

В следующем коде:

class A {
    private int number;

    public void a() {
        number = 5;
    }

    public void b() {
        while(number == 0) {
            // ...
        }
    }
}

Если вызывается метод b, а затем запускается новый поток, который запускает метод a, тогда метод b не гарантирует когда-либо увидеть изменение number и, следовательно, b никогда не может завершиться.

Конечно, мы могли бы сделать number volatile, чтобы решить эту проблему. Однако по академическим причинам допустим, что volatile не является вариантом:

Часто задаваемые вопросы по JSR-133:

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

Похоже, мне просто нужны как a, так и b для входа и выхода любого synchronized -Block вообще, независимо от того, какой монитор они используют. Точнее, это звучит так:

class A {
    private int number;

    public void a() {
        number = 5;
        synchronized(new Object()) {}
    }

    public void b() {
        while(number == 0) {
            // ...
            synchronized(new Object()) {}
        }
    }
}

... устранит проблему и будет гарантировать, что b увидит изменение на a и, следовательно, также закончится.

Однако в часто задаваемых вопросах также четко указано:

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

synchronized (new Object()) {}

Это фактически не-op, и ваш компилятор может полностью удалить его, потому что компилятор знает, что ни один другой поток не будет синхронизироваться тот же монитор. Вы должны установить связь между событиями и один поток, чтобы увидеть результаты другого.

Теперь это сбивает с толку. Я думал, что синхронизированный оператор вызовет сброс кешей. Конечно, он не может очистить кеш от основной памяти так, что изменения в основной памяти могут отображаться только потоками, которые синхронизируются на одном мониторе, тем более что для volatile, который в основном делает то же самое, нам даже не нужно монитор, или я ошибаюсь там? Итак, почему это не-op и не вызывает b завершение гарантии?

4b9b3361

Ответ 1

Часто задаваемые вопросы не являются полномочиями по этому вопросу; JLS. Раздел 17.4.4 определяет синхронизацию - с отношениями, которые передаются в отношения -отношения (17.4.5). Соответствующий пункт указывает:

  • Действие разблокировки на мониторе m синхронизируется со всеми последующими действиями блокировки на m (где "последующее" определяется в соответствии с порядком синхронизации).

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

Технически вам даже не нужно делать полный кеш-флеш, чтобы соответствовать спецификации JLS; это больше, чем требует JLS. Типичная реализация делает это, потому что это самая простая вещь, которую позволяет аппаратное обеспечение, но это происходит "выше и дальше", так сказать. В тех случаях, когда анализ утечки сообщает оптимизирующему компилятору, что нам нужно еще меньше, компилятор может выполнять меньше. В вашем примере анализ escape-кода может сообщить компилятору, что действие не имеет эффекта (из-за рассуждений выше) и может быть полностью оптимизировано.

Ответ 2

следующий шаблон, который некоторые используют для принудительного барьера памяти, не работает:

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

Я думал, что синхронизированный-Statement вызовет сброс кешей

В спецификации языка Java нет "кеша". Это концепция, которая существует только в деталях некоторых (ну, O.K., практически всех) аппаратных платформ и реализаций JVM.