Является ли java удалять/оптимизировать ненужные синхронизированные операторы?

Предположим, что кто-то синхронизирует метод, возвращающий int:

int whatever = 33;
...

public synchronized int getWathever() {
    return this.whatever;
}

Мы знаем из спецификаций Java, что ints изменяются атомарно. Следовательно, оператор synchronized не нужен.

Удалят/оптимизируют ли компиляторы?

Ответ 1

Нет, это просто невозможно. Если бы компилятор и JVM могли это сделать, вполне вероятно, что ограничения, установленные моделью памяти языка программирования Java, будут нарушены.

Более конкретно, порядок порядок синхронизации, указанный в Спецификации языка Java, будет нарушен. Если компилятор или JVM * должны были удалить любую "нежелательную" синхронизацию, то любая дальнейшая сделанная оптимизация нарушала бы любые предположения, размещенные разработчиком в отношении порядка синхронизации (и события-раньше). В вашем конкретном случае любая запись в целое будет происходить до чтения в компиляторе /JVM, который подчиняется модели памяти Java.

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

* Примечание. Ссылка на дуплекс компилятора /JVM является преднамеренной. Компилятор выдает только байт-код, который соответствует JLS; JVM может просто иметь ошибку, где требования модели памяти все еще могут быть нарушены. Для полноты модели памяти как компилятор, так и JVM должны соответствовать требованиям, установленным моделью памяти.

Ответ 2

synchronized имеет другие эффекты безопасности потока, помимо получения блокировки (гарантируя, что измененные данные видны другим потокам для одного)

пока эти побочные эффекты действительны, JIT по существу свободен делать то, что хочет

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

Ответ 3

Бывают случаи, когда VM может устранить блокировку. Например

Анализ побега

int bar()
    Foo foo = new Foo();
    return foo.getWhatever();

VM может рассуждать о том, что foo не виден никому другому потоку, поэтому никто не попытается заблокировать его, поэтому блокировка метода getWhatever может быть удалена.

Блокировка упрочнения

Foo foo = ...;
void bar()
    a();
    foo.getWhatever();
    b();
    foo.getWhatever();
    c();

может быть юридически объединено для сохранения одного действия блокировки

void bar()
    synchronized(foo)
        a();
        foo.getWhatever_without_lock();
        b();
        foo.getWhatever_without_lock();
        c();

Еще одна хорошая новость заключается в том, что блокированная область настолько коротка, что благодаря адаптивной блокировке VM, скорее всего, будет использовать блокировку спина; подвеска из-за неудачной блокировки очень маловероятна.

Ответ 4

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