Java, лениво инициализированное поле без синхронизации - PullRequest
6 голосов
/ 06 апреля 2011

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

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;
  }
}

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

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

  • loadDictionary всегда возвращает одни и те же данные.
  • loadDictionary метод является поточно-ориентированным.

Мои вопросы:

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

Ответы [ 7 ]

3 голосов
/ 06 апреля 2011

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

public class Something {

        private Something() {}

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

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

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

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

3 голосов
/ 06 апреля 2011

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

public enum Dictionary {
 INSTANCE;

  private Dictionary() {
     // load dictionary
  }
}

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

РЕДАКТИРОВАТЬ: Если словарь необходимо расширить список или карту, вы можете сделать.

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() { }
}
2 голосов
/ 06 апреля 2011

Ваш код правильный. Чтобы избежать загрузки более одного раза, 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;
2 голосов
/ 06 апреля 2011

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

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;
  }
}
2 голосов
/ 06 апреля 2011

1.Этот шаблон правильный? Другими словами, возможно ли, чтобы getDictionary () возвратил неверные данные?

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

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

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

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

Пока вам нужно решение без синхронизации (явное или неявное) - нет (насколько я понимаю). Иначе, есть много идиом, таких как использование enum или класс внутреннего держателя (но они используют неявную синхронизацию).

1 голос
/ 06 апреля 2011

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

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

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

0 голосов
/ 06 апреля 2011

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

Этот метод основан на том, что JVM инициализирует членов класса только при первой ссылке на класс.В этом случае у нас есть внутренний класс, на который ссылается только метод 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();
  }
}
...