Как решить проблему с объявлением «Double-Checked Locking is Broken» в Java?

Я хочу реализовать отложенную инициализацию для многопоточности в Java.
У меня есть некоторый код вида:

class Foo {
    private Helper helper = null;
    public Helper getHelper() {
        if (helper == null) {
            Helper h;
            synchronized(this) {
                h = helper;
                if (h == null) 
                    synchronized (this) {
                        h = new Helper();
                    } // release inner synchronization lock
                helper = h;
            } 
        }    
        return helper;
    }
    // other functions and members...
}

И я ' m получение объявления "Double-Checked Locking is Broken".
Как я могу решить эту проблему?

33
задан Hosam Aly 15 October 2014 в 11:53
поделиться

6 ответов

Вот идиома, рекомендованная в Пункте 71: Разумно используйте ленивую инициализацию Эффективная Java:

Если вам нужно использовать ленивую инициализацию для повышения производительности на поле экземпляра, используйте двойную проверку идиома. Эта идиома позволяет избежать затрат блокировки при доступе к полю после его инициализации (пункт 67).Идея идиомы состоит в том, чтобы проверьте значение поля дважды (отсюда и название двойная проверка): один раз без блокировки, а затем, если поле выглядит неинициализированным, второй раз с блокировкой. Только если вторая проверка указывает, что поле является неинициализированным вызовом инициализировать поле. Потому что это нет блокировки, если поле уже инициализированы, критически поле должно быть объявлено volatile (Item 66). Вот идиома:

// Идиома двойной проверки для ленивой инициализации полей экземпляра
частное изменяемое поле FieldType;
частный тип поля getField () {
Результат FieldType = поле;
if (result != null) // Первая проверка (без блокировки)
вернуть результат;
синхронизированный (это) {
if (field == null) // Вторая проверка (с блокировкой)
поле = вычислить значение поля();
поле возврата;
 }
}

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

До версии 1.5 перепроверка идиома не работала надежно, потому что семантика модификатора volatile не были достаточно сильны, чтобы поддержать его [Пью01]. Представлена ​​модель памяти в версии 1.5 исправлена ​​эта проблема [JLS, 17, Goetz06 16].Сегодня двойная проверка идиома выбор для ленивой инициализации поле экземпляра. Пока вы можете подать заявку идиома перепроверить статику поля, нет причин сделать так: держатель ленивой инициализации классовая идиома - лучший выбор.

Справочник

  • Эффективная Java, второе издание
    • Item 71: Используйте ленивую инициализацию с умом
73
ответ дан 27 November 2019 в 17:36
поделиться

Вот шаблон правильной блокировки с двойной проверкой.

class Foo {

  private volatile HeavyWeight lazy;

  HeavyWeight getLazy() {
    HeavyWeight tmp = lazy; /* Minimize slow accesses to `volatile` member. */
    if (tmp == null) {
      synchronized (this) {
        tmp = lazy;
        if (tmp == null) 
          lazy = tmp = createHeavyWeightObject();
      }
    }
    return tmp;
  }

}

Для синглтона есть гораздо более удобочитаемая идиома для ленивой инициализации.

class Singleton {
  private static class Ref {
    static final Singleton instance = new Singleton();
  }
  public static Singleton get() {
    return Ref.instance;
  }
}
12
ответ дан 27 November 2019 в 17:36
поделиться

Определите переменную, которая должна быть дважды проверена с помощью volatile. ] midifier

Вам не нужна переменная h. Вот пример из здесь

class Foo {
    private volatile Helper helper = null;
    public Helper getHelper() {
        if (helper == null) {
            synchronized(this) {
                if (helper == null)
                    helper = new Helper();
            }
        }
        return helper;
    }
}
2
ответ дан 27 November 2019 в 17:36
поделиться

что вы имеете в виду, от кого вы получаете декларацию?

Исправлена ​​блокировка с двойной проверкой. проверьте википедию:

public class FinalWrapper<T>
{
    public final T value;
    public FinalWrapper(T value) { this.value = value; }
}

public class Foo
{
   private FinalWrapper<Helper> helperWrapper = null;
   public Helper getHelper()
   {
      FinalWrapper<Helper> wrapper = helperWrapper;
      if (wrapper == null)
      {
          synchronized(this)
          {
              if (helperWrapper ==null)
                  helperWrapper = new FinalWrapper<Helper>( new Helper() );
              wrapper = helperWrapper;
          }
      }
      return wrapper.value;
   }
2
ответ дан 27 November 2019 в 17:36
поделиться

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

Нам надоели постоянные проблемы с тем, что люди понимают это неправильно, поэтому мы написали утилиту LazyReference с окончательной семантикой, которая была профилирована и настроена так, чтобы работать как можно быстрее.

2
ответ дан 27 November 2019 в 17:36
поделиться

Единственный способ правильно выполнить блокировку с двойной проверкой в ​​Java — использовать «изменчивые» объявления рассматриваемой переменной. Хотя это решение правильное, обратите внимание, что «изменчивый» означает, что строки кэша сбрасываются при каждом доступе. Поскольку «синхронизированный» сбрасывает их в конец блока, на самом деле он может быть не более эффективным (или даже менее эффективным). Я бы рекомендовал просто не использовать блокировку с двойной проверкой, если только вы не профилировали свой код и не обнаружили проблемы с производительностью в этой области.

3
ответ дан 27 November 2019 в 17:36
поделиться