Синглтон с отложенной загрузкой: двойная проверка блокировки и инициализация по требованию - PullRequest
9 голосов
/ 31 мая 2011

У меня есть требование для отложенной загрузки ресурсов в параллельной среде.Код для загрузки ресурсов должен быть выполнен только один раз.

Оба Двойная проверка блокировки (с использованием JRE 5+ и ключевое слово volatile) и Инициализация по требованию идиома держателя похоже, хорошо подходит для работы.

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

Мой вопрос здесь: какой подход лучше?И почему?Если ваш ответ - нет.Как бы вы справились с этим требованием в среде Java SE?

Альтернативы

Могу ли я использовать CDI для этого без наложения его на весь мой проект?Есть какие-нибудь статьи?

Ответы [ 5 ]

7 голосов
/ 01 июня 2011

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

У нас есть следующие опции:

Supplier<ExpensiveThing> t1 = new LazyReference<ExpensiveThing>() {
  protected ExpensiveThing create() {
    … // expensive initialisation
  }
};

Supplier<ExpensiveThing> t2 = Lazy.supplier(new Supplier<ExpensiveThing>() {
  public ExpensiveThing get() {
    … // expensive initialisation
  }
});

Обаимеют одинаковую семантику в отношении использования.Вторая форма делает любые ссылки, используемые внутренним поставщиком, доступными для GC после инициализации.Вторая форма также поддерживает тайм-ауты с помощью стратегий TTL / TTI.

7 голосов
/ 31 мая 2011

Что касается читабельности, я бы пошел с инициализацией по требованию владельца. Я считаю, что блокировка с двойной проверкой - устаревшая и уродливая реализация.

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

7 голосов
/ 31 мая 2011

Чтобы добавить еще один, возможно, более чистый вариант.Я предлагаю вариант enum:

Каков наилучший подход для использования Enum в качестве синглтона в Java?

5 голосов
/ 12 июня 2012

Держатель инициализации по требованию всегда является наилучшей практикой для реализации одноэлементного шаблона. Он очень хорошо использует следующие функции JVM.

  1. Статические вложенные классы загружаются только при вызове по имени.
  2. Механизм загрузки классов по умолчанию защищен от параллелизма. Поэтому, когда поток инициализирует класс, другие потоки ожидают его завершения.

Кроме того, вам не нужно использовать ключевое слово синхронизации, оно замедляет вашу программу в 100 раз.

3 голосов
/ 31 мая 2011

Я подозреваю, что инициализация по требованию владельца немного быстрее, чем дважды проверенная блокировка (с использованием volatile). Причина в том, что у первого нет никаких накладных расходов на синхронизацию после создания экземпляра, но последнее включает чтение энергозависимого, что (я думаю) влечет за собой полное чтение памяти.

Если производительность не является существенной проблемой, то синхронный подход getInstance() является самым простым.

...