Java, лениво инициализированное поле без синхронизации

иногда, когда мне нужно лениво инициализированное поле, я использую следующий шаблон дизайна.

class DictionaryHolder {
  private volatile Dictionary dict; // some heavy object

  public Dictionary getDictionary() {
    Dictionary d = this.dict;
    if (d == null) {
      d = loadDictionary(); // costy operation
      this.dict = d;
    }
    return d;
  }
}

похоже на двойную проверку idion, но не совсем. Синхронизации нет, и это возможно для loadDictionary метод вызывается несколько раз.

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

  • loadDictionary метод всегда возвращает тот же самый данные.
  • loadDictionary метод потокобезопасен.

мои вопросы:

  1. этот шаблон правильно? Другими словами, возможно ли getDictionary() возвращать неверные данные?
  2. можно сделать dict поле энергонезависимое для большей эффективности?
  3. есть ли лучшее решение?

7 ответов


1.Правильно ли это? Другими словами, возможно ли getDictionary () возвращать недопустимые данные?

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

2.Можно ли сделать поле dict энергонезависимым для большей эффективности?

нет, это может вызвать память проблемы с видимостью.

3.Есть ли лучшее решение?

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


Я лично считаю, что инициализация по требованию владельца идиома хорошо подходит для этого случая. Из Вики:

public class Something {

        private Something() {}

        private static class LazyHolder {
                private static final Something INSTANCE = new Something();
        }

        public static final Something getInstance() {
                return LazyHolder.INSTANCE;
        }
}

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

кроме того, кажется, что в вашем случае, если несколько потоков очереди на вызов loadDictionary (который синхронизируется), вы можете в конечном итоге загрузить то же самое всего несколько раз.


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

public enum Dictionary {
 INSTANCE;

  private Dictionary() {
     // load dictionary
  }
}

не должно быть необходимости делать его более сложным, конечно, вы не сделаете его более эффективным.

EDIT: если словарь нужно расширить список или карту вы можете сделать.

public enum Dictionary implements List<String> { }

или лучше использовать поля.

public enum Dictionary {
    INSTANCE;
    public final List<String> list = new ArrayList<String>();
}

или использовать статический блок инициализации

public class Dictionary extends ArrayList<String> {
    public static final Dictionary INSTANCE = new Dictionary();
    private Dictionary() { }
}

ваш код правильный. Чтобы избежать загрузки более одного раза,synchronized{} было бы неплохо.

вы можете удалить volatile, если Dictionary незыблем.

private Dictionary dict; // not volatile; assume Dictionary immutable

public Dictionary getDict()
    if(dict==null)
        dict = load()
    return dict;

если мы добавим двойную проверку блокировки, это идеально

public Dictionary getDict()
    if(dict==null)
        synchronized(this)
             if(dict==null)
                  dict = load()
    return dict;

двойная проверка блокировки отлично подходит для неизменяемых объектов, без необходимости летучих.


к сожалению выше 2 getDict() методы теоретически не являются пуленепробиваемыми. Слабая модель памяти java позволит некоторым жутким действия - в теории. Чтобы быть на 100% правильно по книге, мы должны добавить локальную переменную, которая загромождает наш код:

public Dictionary getDict()
    Dictionary local = dict;
    if(local==null)
        synchronized(this)
             local = dict;
             if(local==null)
                  local = dict = load()
    return local;

просто быстрый удар по этому, но как насчет...

class DictionaryHolder {
  private volatile Dictionary dict; // some heavy object

  public Dictionary getDictionary() {
    Dictionary d = this.dict;
    if (d == null) {
      synchronized (this) {
        d = this.dict;
        if (d == null) { // gated test for null
          this.dict = d = loadDictionary(); // costy operation
        }
    }
    return d;
  }
}

возможно ли сделать поле dict энергонезависимым для большей эффективности?

нет. Это было бы больно!--6-->видимость, т. е. когда один поток инициализирует dict, другие потоки могут не видеть обновленную ссылку во времени (или вообще). Это, в свою очередь, приведет к нескольким тяжелым инициализациям, таким образом много бесполезной работы , не говоря уже о возврате ссылок на несколько различных объектов.

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


идиома класса держателя инициализации по требованию

этот метод полагается только на JVM intializing члены класса на первая ссылка на класс. В этом кейс, у нас есть внутренний класс, который ссылаться только в getDictionary() метод. Это средство DictionaryHolder будет инициализирован при первом вызове getDictionary ().

public class DictionaryHolder {

  private DictionaryHolder ()
  {
  }

  public static Dictionary getDictionary() 
  {
     return DictionaryLazyHolder.instance;
  }

  private static class DictionaryLazyHolder
  {
    static final DictionaryHolder instance = new DictionaryHolder();
  }
}