Зачем использовать как локальные переменные, так и volatile вместе для DCL (как предложено инструментом Sonar)? - PullRequest
0 голосов
/ 28 мая 2019

Есть связанные вопросы , но они в основном фокусируются на нестабильности, а не на использовании локальной переменной, поэтому я отправляю новый вопрос, см. Вопрос ниже.

Источник сонара Правило имеет код, в соответствии с которым приведенный ниже код является правильным, и решает проблемы с DCL

class ResourceFactory {
  private volatile Resource resource;

  public Resource getResource() {
    Resource localResource = resource;
    if (localResource == null) {
      synchronized (this) {
        localResource = resource;
        if (localResource == null) {
          resource = localResource = new Resource();
        }
      }
    }
    return localResource;
  }

  static class Resource {
  }
}

Я понимаю, что у нас есть альтернативные варианты (некоторые лучше, чем сложные DCL), такие как

  1. enum или
  2. статический внутренний классодержатель или
  3. статическая конечная ссылка, инициализированная во время самого объявления.
  4. объявить весь метод синхронизированным.

Я хочу понять использование volatile вместе с локальной переменной для решения проблем с DCL.

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

Вопрос 1 Я хочу понять, для чего нужна переменная, если локальная переменная уже использовалась. Ниже в коде есть энергонезависимая переменная resource, но внутри getResource() локальная переменная используется для чтения resource. Есть ли еще проблемы в коде? (см. Комментарии ниже по коду ниже)

class ResourceFactory {
  private Resource resource; // non volatile
  public Resource getResource() {
    Resource localResource = resource; // only 1 read without lock
    if (localResource == null) {
      synchronized (this) {
        localResource = resource; // read inside the synchronized block
        if (localResource == null) {
           localResource = new Resource(); // constructor is initialized. 
           resource = localResource; // ****code reaches here only when constructor has been complete. Here is there a chance of resource being referring to incomplete object ?**** 
        }
      }
    }
    return localResource;
  }

  static class Resource {
  }
}

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

Вопрос 2 Вместо использования локальной переменной, если использовалась простая переменная, тогда был ли код без проблем?

1 Ответ

2 голосов
/ 28 мая 2019

Вопрос 2 напрямую рассматривается в Эффективная Java ; хотя это может быть упомянуто и в других изданиях, передо мной стоит 3-е изд , пункт 83, с. 335:

Код может показаться немного запутанным. В частности, необходимость локальной переменной (result) может быть неясной. Эта переменная гарантирует, что field читается только один раз в общем случае, когда она уже инициализирована. Хотя это и не является строго необходимым, это может повысить производительность и стать более элегантным с помощью стандартов, применяемых к низкоуровневому параллельному программированию. На моей машине описанный выше метод примерно в 1,4 раза быстрее, чем очевидная версия без локальной переменной.

(Обратите внимание, что в 3-м издании имеется серьезная ошибка в образце кода для двойной проверки блокировки (непосредственно перед этой цитатой) в ранних печатных изданиях! Проверьте исправления для исправленного кода.)

...