Синхронизированное ключевое слово Java очищает кеш?

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

Вот код для ленивой инициализации singleton:

public final class MySingleton {
  private static MySingleton instance = null;
  private MySingleton() { } 
  public static MySingleton getInstance() {
    if (instance == null) {
      synchronized (MySingleton.class) {
        if (instance == null) {
          instance = new MySingleton();
        }
      }
    }
    return instance;
  }
}

Следует ли объявлять instance volatile, чтобы оптимизатор не переписывал getInstance() следующим образом (что было бы правильным в последовательной программе):

public static MySingleton getInstance() {
  if (instance == null) {
    synchronized (MySingleton.class) {
      // instance must be null or we wouldn't be here  (WRONG!)
      instance = new MySingleton();
    }
  }
}

Предполагая, что оптимизатор не переписывает код, если instance не объявлен volatile, он все равно будет сбрасываться в память при выходе из блока synchronized и считываться из памяти, когда блок synchronized вводится?

EDIT: я забыл сделать getInstance() static. Я не думаю, что это изменяет правильность ответов; вы все знали, что я имел в виду.

Ответ 1

Да, instance должен быть объявлен volatile. Даже тогда рекомендуется не использовать блокировку с двойной проверкой. Он (или, точнее, модель памяти Java) имел серьезный недостаток, который позволял публиковать частично реализованные объекты. Это было исправлено в Java5, но DCL - устаревшая идиома, и больше не нужно использовать его - вместо этого используйте ленивую идентификацию владельца инициализации.

От Java Concurrency на практике, раздел 16.2:

Реальная проблема с DCL - это предположение, что худшее, что может случиться при чтении ссылки на общий объект без синхронизации, - это ошибочно увидеть устаревшее значение (в данном случае null); в этом случае идиома DCL компенсирует этот риск, снова попробовав блокировку. Но худший случай на самом деле значительно хуже - можно увидеть текущее значение ссылочных, но устаревших значений для состояния объекта, что означает, что объект может считаться недопустимым или неправильным.

Последующие изменения в JMM (Java 5.0 и новее) позволили DCL работать, если resource сделано volatile, а влияние производительности этого мало, так как volatile чтение обычно немного дороже, чем энергонезависимая читает. Тем не менее, это идиома, чья полезность в значительной степени прошла - силы, которые ее мотивировали (медленная незащищенная синхронизация, медленный запуск JVM), больше не играют в игру, что делает ее менее эффективной как оптимизация. Ленькая идиома держателя инициализации предлагает те же преимущества и легче понять.

Ответ 2

Да, экземпляр должен быть изменчивым с помощью двойной проверки блокировки в Java, поскольку в противном случае инициализация MySingleton может выставить частично построенный объект для остальной части системы. Также верно, что потоки будут синхронизироваться, когда они достигнут "синхронизированного" оператора, но это слишком поздно в этом случае.

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

Ответ 3

Там более безопасная и читаемая идиома для ленивой инициализации синглетов Java:

class Singleton {

  private static class Holder {
    static final Singleton instance = create();
  }

  static Singleton getInstance() { 
    return Holder.instance; 
  }

  private Singleton create() {
    ⋮
  }

  private Singleton() { }

}

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

Ответ 5

Нет. Он не должен быть изменчивым. См. Как разрешить "Блокировка с двойной проверкой" . Декларация на Java?

предыдущие попытки потерпели неудачу, потому что если вы можете обмануть java и избежать volatile/sync, java может обмануть вас и дать вам неправильный вид объекта. однако новая семантика final решает проблему. если вы получите ссылку на объект (обычным чтением), вы можете спокойно прочитать его поля final.