Почему исходный код JDK принимает "окончательную" копию "неустойчивых" экземпляров

Я прочитал исходный код JDK о ConcurrentHashMap.

Но следующий код меня смутил:

public boolean isEmpty() {
    final Segment<K,V>[] segments = this.segments;
    ...
}

Мой вопрос:

"this.segments" объявлено:

final Segment<K,V>[] segments;

Итак, здесь, в начале метода, объявленного ссылкой на тот же тип, укажите на ту же память.

Почему автор написал это так? Почему они не использовали this.segments напрямую? Есть ли какая-то причина?

Ответ 1

Это идиома, типичная для кода без блокировки с переменными volatile. На первой строке вы читаете volatile один раз, а затем работаете с ним. Тем временем другой поток может обновить volatile, но вас интересует только что прочитанное значение.

Кроме того, даже если переменная-член не является изменчивой, но окончательной, эта идиома связана с кэшами ЦП, поскольку чтение из местоположения стека более безопасно для кэша, чем чтение из случайного местоположения кучи. Существует также более высокая вероятность того, что локальная переменная будет привязана к регистру CPU.

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

Ответ 2

Я предполагаю, что это нужно для рассмотрения производительности, так что нам нужно только один раз получить значение поля.

Вы можете ссылаться на одиночную идиому из эффективной java Джошуа Блоха

Его синглтон находится здесь:

private volatile FieldType field;
FieldType getField() {
  FieldType result = field;
  if (result == null) { 
    synchronized(this) {
      result = field;
      if (result == null) 
        field = result = computeFieldValue();
    }
  }
  return result;
}

и он написал:

Этот код может казаться немного запутанным. В частности, необходимость результат локальной переменной может быть неясным. То, что делает эта переменная, - это убедитесь, что поле читается только один раз в обычном случае, когда его уже инициализирован. Хотя это не является строго необходимым, это может улучшить производительность и является более элегантным по стандартам, применяемым к низкоуровневым параллельное программирование. На моей машине вышеописанный метод составляет около 25 % быстрее, чем очевидная версия без локальной переменной.

Ответ 3

Он может уменьшить размер байтового кода - доступ к локальной переменной короче в байтовом коде, чем доступ к переменной экземпляра.

Накладные расходы оптимизации времени выполнения также могут быть уменьшены.

Но ни один из них не является значительным. Это больше о стиле кода. Если вы чувствуете себя комфортно с переменными экземпляра, во что бы то ни стало. Дуг Ли, вероятно, чувствует себя более комфортно в отношении локальных переменных.