![Паттерны проектирования: Singleton - 1](https://cdn.javarush.com/images/article/226faa2c-6d47-48a7-8312-5abe29894e72/800.jpeg)
Что такое синглтон?
Синглтон — это один из самых простых шаблонов (паттернов) проектирования, который применяется к классу. Иногда говорят: “этот класс — синглтон”, подразумевая, что этот класс реализует паттерн проектирования синглтон. Иногда необходимо написать класс, у которого можно будет создать только один объект. Например, класс, отвечающий за логирование или подключение к базе данных. Шаблон проектирования синглтон описывает, как мы можем выполнить такую задачу. Синглтон — это шаблон (паттерн) проектирования, который делает две вещи:Дает гарантию, что у класса будет всего один экземпляр класса.
Предоставляет глобальную точку доступа к экземпляру данного класса.
Приватный конструктор. Ограничивает возможность создания объектов класса за пределами самого класса.
Публичный статический метод, который возвращает экземпляр класса. Данный метод называют
getInstance
. Это глобальная точка доступа к экземпляру класса.
Варианты реализации
Шаблон проектирования синглтон применяют по-разному. Каждый вариант по-своему хорош и плох. Тут как всегда: идеала нет, но нужно к нему стремиться. Но прежде всего давай определимся, что такое хорошо и что такое плохо, и какие метрики влияют на оценку реализации шаблона проектирования. Начнем с положительного. Вот критерии, которые придают реализации сочности и привлекательности:Ленивая инициализация: когда класс загружается во время работы приложения именно тогда, когда он нужен.
Простота и прозрачность кода: метрика, конечно, субъективная, но важная.
Потокобезопасность: корректная работа в многопоточной среде.
Высокая производительность в многопоточной среде: потоки блокируют друг друга минимально, либо вообще не блокируют при совместном доступе к ресурсу.
Не ленивая инициализация: когда класс загружается при старте приложения, независимо от того, нужен он или нет (парадокс, в мире IT лучше быть лентяем)
Сложность и плохая читаемость кода. Метрика также субъективная. Будем считать, что если кровь пошла из глаз, реализация так себе.
Отсутствие потокобезопасности. Иными словами, “потокоопасность”. Некорректная работа в многопоточной среде.
Низкая производительность в многопоточной среде: потоки блокируют друг друга все время либо часто, при совместном доступе к ресурсу.
Код
Теперь мы готовы рассмотреть различные варианты реализации с перечислением плюсов и минусов:Simple Solution
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return INSTANCE;
}
}
Самая простая реализация.
Плюсы:
Простота и прозрачность кода
Потокобезопасность
Высокая производительность в многопоточной среде
- Не ленивая инициализация.
Lazy Initialization
public class Singleton {
private static Singleton INSTANCE;
private Singleton() {}
public static Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
Плюсы:
Ленивая инициализация.
Не потокобезопасно
Synchronized Accessor
public class Singleton {
private static Singleton INSTANCE;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
Плюсы:
Ленивая инициализация.
Потокобезопасность
Низкая производительность в многопоточной среде
getInstance
синхронизирован, и входить в него можно только по одному.
На самом деле нам нужно синхронизировать не весь метод, а лишь ту его часть, в которой мы инициализируем новый объект класса. Но мы не можем просто обернуть в synchronized
блок часть, отвечающую за создание нового объекта: это не обеспечит потокобезопасность. Все немного сложнее.
Правильный способ синхронизации представлен ниже:
Double Checked Locking
public class Singleton {
private static Singleton INSTANCE;
private Singleton() {
}
public static Singleton getInstance() {
if (INSTANCE == null) {
synchronized (Singleton.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
Плюсы:
Ленивая инициализация.
Потокобезопасность
Высокая производительность в многопоточной среде
Не поддерживается на версиях Java ниже 1.5 (в версии 1.5 исправили работу ключевого слова volatile)
INSTANCE
должна быть либо final
, либо volatile
.
Последняя реализация, которую мы сегодня обсудим, — Class Holder Singleton
.
Class Holder Singleton
public class Singleton {
private Singleton() {
}
private static class SingletonHolder {
public static final Singleton HOLDER_INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.HOLDER_INSTANCE;
}
}
Плюсы:
Ленивая инициализация.
Потокобезопасность.
Высокая производительность в многопоточной среде.
Для корректной работы необходима гарантия, что объект класса
Singleton
инициализируется без ошибок. Иначе первый вызов методаgetInstance
закончится ошибкойExceptionInInitializerError
, а все последующиеNoClassDefFoundError
.
Реализация | Ленивая инициализация | Потокобезопасность | Скорость работы при многопоточности | Когда использовать? |
---|---|---|---|---|
Simple Solution | - | + | Быстро | Никогда. Либо когда не важна ленивая инициализация. Но лучше никогда. |
Lazy Initialization | + | - | Неприменимо | Всегда, когда не нужна многопоточность |
Synchronized Accessor | + | + | Медленно | Никогда. Либо когда скорость работы при многопоточности не имеет значения. Но лучше никогда |
Double Checked Locking | + | + | Быстро | В редких случаях, когда нужно обрабатывать исключения при создании синглтона. (когда неприменим Class Holder Singleton) |
Class Holder Singleton | + | + | Быстро | Всегда, когда нужна многопоточность и есть гарантия, что объект синглтон класса будет создан без проблем. |
Плюсы и минусы паттерна Singleton
В целом синглтон делает именно то, что от него ждут:Дает гарантию, что у класса будет всего один экземпляр класса.
Предоставляет глобальную точку доступа к экземпляру данного класса.
Синглтон нарушает SRP (Single Responsibility Principle) — класс синглтона, помимо непосредственных обязанностей, занимается еще и контролированием количества своих экземпляров.
Зависимость обычного класса или метода от синглтона не видна в публичном контракте класса.
Глобальные переменные это плохо. Синглтон превращается в итоге в одну здоровенную глобальную переменную.
Наличие синглтона снижает тестируемость приложения в целом и классов, которые используют синглтон, в частности.