Правильный Синглтон В Java

Я уверен, что каждый из читателей знает, что такое шаблон проектирования «Синглтон», но не каждый знает, как его эффективно и правильно программировать.

Данная статья представляет собой попытку объединить существующие знания по данному вопросу.

Кроме того, статью можно рассматривать как продолжение.

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



Неленивый синглтон на Java
Автору известны два способа реализации шаблона с обычной инициализацией.

1 статическое поле

  
  
  
  
   

public class Singleton { public static final Singleton INSTANCE = new Singleton(); }

+ Простая и прозрачная реализация + Безопасность резьбы - Не ленивая инициализация 2 Перечисление синглтона По мнению Джошуа Блоха, это лучший способ реализовать шаблон [1].



public enum Singleton { INSTANCE; }

+ Остроумный + Сериализация «из коробки» + Безопасность резьбы прямо из коробки + Возможность использования EnumSet, EnumMap и т. д. + поддержка переключения - Не ленивая инициализация

Ленивый синглтон на Java
На момент написания статьи существует как минимум три допустимых реализации шаблона Singleton с ленивой инициализацией в Java. 1 синхронизированный аксессуар

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

+ Ленивая инициализация - Плохая производительность (критическая секция) при наиболее типичном доступе 2 Двойная проверка Блокировка и нестабильность

public class Singleton { private static volatile Singleton instance; public static Singleton getInstance() { Singleton localInstance = instance; if (localInstance == null) { synchronized (Singleton.class) { localInstance = instance; if (localInstance == null) { instance = localInstance = new Singleton(); } } } return localInstance; } }

+ Ленивая инициализация + Высокая производительность - Поддерживается только в JDK 1.5 [5] 2.1 Почему не работает без волатиля? Проблема с идиомой двойной проверки блокировки заключается в модели памяти Java, а точнее в порядке создания объектов.

Условно этот порядок можно представить следующими этапами [2, 3]: Давайте создадим нового студента: Student s = new Student(), затем 1) local_ptr = malloc(sizeof(Student)) //выделяем память под сам объект; 2) s = local_ptr // инициализация указателя; 3) Студент::ктор(ы); // конструирование объекта (инициализация полей); Таким образом, между вторым и третьим этапами другой поток может получить и начать использовать (при условии, что указатель не равен нулю) не полностью созданный объект. Фактически эта проблема была частично решена в JDK 1.5 [5], но авторы JSR-133 [5] рекомендуют использовать voloatile для блокировки с двойной проверкой.

Причем их отношение к подобным вещам легко увидеть из комментария к спецификации:

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

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

Таким образом, хотя проблема и решена, использование Double Checked Lock без энергозависимой крайне опасно.

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

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

Наконец, Double Checked Lock можно использовать без исключения с неизменяемыми объектами (String, Integer, Float и т. д.).

3 Идиома держателя по требованию

public class Singleton { public static class SingletonHolder { public static final Singleton HOLDER_INSTANCE = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.HOLDER_INSTANCE; } }

+ Ленивая инициализация + Высокая производительность - Невозможно использовать для нестатических полей класса.



Производительность
Для сравнения производительности вышеописанных методов использовался микро-бенчмарк [6], определяющий количество элементарных операций (приращений полей) в секунду на объекте Singleton из двух параллельных потоков.

Измерения проводились на двухъядерном процессоре Intel Core 2 Duo T7300 2 ГГц, 2 Гб оперативной памяти и клиентской виртуальной машине Java HotSpot™ (сборка 17.0-b17).

Единицей скорости считается количество приращений (и, следовательно, захватов объектов) в секунду * 100 000. (чем больше, тем лучше)

Клиент Сервер
Синхронизированный метод доступа 42,6 86,3
Двойная проверка блокировки и нестабильность 179,8 202,4
Держатель по требованию 181,6 202,7
Вывод: если правильно выбрать реализацию шаблона, то можно получить ускорение от 2x до 4x .



Краткое содержание
Можно выделить следующие краткие советы по использованию того или иного подхода для реализации паттерна «Единый» [1].

1) Используйте обычную (не ленивую) инициализацию везде, где это возможно; 2) Для статических полей используйте идентификатор держателя по требованию; 3) Для простых полей используйте Double Chedked Lock и volutable idom; 4) Во всех остальных случаях используйте синхронизированный метод доступа;

Библиотека классов Java и синглтон
Примечательно, что разработчики библиотеки классов Java выбрали самый простой способ реализации паттерна — Syncronized Accessor. С одной стороны, это гарантия совместимости и корректной работы.

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

Быстрый поиск по grep в исходном коде дал понять, что таких мест в JCL очень много.

Возможно, следующей статьей будет «Что будет, если все классы Singleton правильно написать в библиотеке классов JavaЭ» :) Ссылки [1] Джошуа Блох, Эффективная перезагрузка Java выступление на Google I/O 2008 ( видео ); [2] Блокировка с двойной проверкой и шаблон Singleton ; [3] Декларация «Двойная проверка блокировки нарушена» ; [4] ru.wikipedia.org/wiki/Double-checked_locking [5] JSR-133 [6] Как писать микротесты Теги: #java #шаблоны проектирования #singleton #блокировка с двойной проверкой #java

Вместе с данным постом часто просматривают: