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

Стоимость использования конечных полей

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

Модель памяти Java гарантирует это final Field Semantics:

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

Это означает, что для такого класса

class X {
    X(int a) {
        this.a = a;
    }
    final int a;

    static X instance;
}   

всякий раз, когда Thread 1 создает экземпляр, подобный этому

X.instance = new X(43);
while (true) doSomethingEventuallyEvictingCache();

и Thread 2 видит его

 while (X.instance == null) {
      doSomethingEventuallyEvictingCache();
 }
 System.out.println(X.instance.a);

он должен распечатать 43. Без модификатора final JIT или CPU могут изменить порядок хранения (сначала сохранить X.instance, а затем установить a=43), а Thread 2 может видеть значение, инициализированное по умолчанию, и напечатать 0 вместо этого.

Когда JIT видит final, он явно воздерживается от переупорядочения. Но это также вынуждает CPU подчиняться приказу. Существует ли связанное с этим ограничение производительности?

4b9b3361

Ответ 1

Есть ли связанное с этим ограничение производительности?

Если вы посмотрите исходный код JIT-компилятора, вы найдете следующий комментарий относительно конечных переменных-членов в файле src/share/vm/opto/parse1.cpp:

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

Компилятор испускает дополнительные инструкции, если есть конечные переменные-члены. Скорее всего, эти дополнительные инструкции приводят к снижению производительности. Но это неясно, если это влияние значимо для любого приложения.