Шаблон одиночки
Шаблон Одиночка — один из простейших паттернов проектирования в Java. Этот тип паттернов проектирования относится к порождающим паттернам, и он предоставляет оптимальный способ создания объектов.
Этот паттерн включает один класс, который отвечает за создание собственного объекта, гарантируя при этом, что создаётся только один объект. Этот класс предоставляет способ прямого доступа к своему уникальному объекту без необходимости создавать экземпляр класса.
Паттерн одиночка — это порождающий паттерн проектирования, который гарантирует, что у класса есть только один экземпляр, и предоставляет глобальную точку доступа к этому экземпляру.
Примечание:
- 1. У класса-одиночки может быть только один экземпляр.
- 2. Класс-одиночка должен создавать свой собственный уникальный экземпляр.
- 3. Класс-одиночка должен предоставлять этот экземпляр всем другим объектам.
summary
Шаблон одиночки
назначение
Обеспечить, чтобы у класса был только один экземпляр, и предоставить глобальную точку доступа к этому экземпляру.
В основном решает
Проблема частого создания и уничтожения глобально используемых экземпляров классов.
Когда использовать
Когда есть необходимость контролировать количество экземпляров и экономить системные ресурсы.
Как решить
Проверьте, существует ли синглтон уже в системе; если существует, верните этот экземпляр; если не существует, создайте новый экземпляр.
Ключевой код
Конструктор является приватным.
Пример приложения
- У класса есть только один классный руководитель.
- Когда Windows работает с файлами в многопроцессной и многопоточной среде, чтобы избежать одновременной работы нескольких процессов или потоков с одним файлом, это нужно обрабатывать через уникальный экземпляр.
- Диспетчер устройств спроектирован по шаблону синглтон. Например, если у компьютера есть два принтера, это предотвращает одновременную печать одного и того же файла.
Преимущества
- В памяти существует только один экземпляр, что снижает накладные расходы на память, особенно когда экземпляры часто создаются и уничтожаются (например, при управлении кэшем главной страницы школы менеджмента).
- Избегать многократного захвата ресурсов (например, операций записи в файл).
Недостатки
- Нет интерфейса, нельзя наследовать.
- Это противоречит принципу единственной ответственности; класс должен заботиться только о своей внутренней логике, а не о том, как он инстанцируется.
Сценарий использования
- Генерировать уникальные серийные номера.
- Счётчик в WEB-разработке, позволяющий избежать увеличения значения счётчика в базе данных при каждом обновлении; вместо этого он сначала кэшируется.
- Создание объектов, потребляющих слишком много ресурсов, таких как I/O и подключения к базе данных.
Примечания
- Потокобезопасность:
getInstance()В методе должны использоваться блокировки синхронизации.synchronized (Singleton.class)Чтобы предотвратить одновременный вход нескольких потоков, из-за которого экземпляр может создаваться несколько раз. - Ленивая инициализация: экземпляр создается при первом вызове
getInstance()метода. - Сериализация и десериализация.: Переопределите
readResolveМетод, чтобы гарантировать, что при десериализации не создается новый экземпляр. - Атака через рефлексию: Добавьте защитный код в конструктор, чтобы предотвратить создание новых экземпляров через рефлексию.
- Проблема загрузчика классов: Учитывайте проблемы с множественными экземплярами, которые могут быть вызваны сложными средами загрузки классов.
Структура
Шаблон одиночка включает следующие основные роли:
- Класс-одиночка: класс, который содержит экземпляр-одиночку и обычно объявляет конструктор приватным.
- Статическая переменная-член: статическая переменная-член, используемая для хранения экземпляра-одиночки.
- Метод получения экземпляра: статический метод, используемый для получения экземпляра-одиночки.
- Закрытый конструктор: предотвращает прямое создание экземпляра класса-одиночки внешним кодом.
- Обработка потокобезопасности: гарантирует, что создание экземпляра одиночки безопасно в многопоточной среде.
Реализация
Мы создадим классSingleObject.SingleObjectКласс имеет свой закрытый конструктор и собственный статический экземпляр.
SingleObjectКласс предоставляет статический метод, с помощью которого внешний код может получить его статический экземпляр.SingletonPatternDemoИспользование классаSingleObjectкласса, чтобы получитьSingleObjectОбъект.

Шаг 1
Создайте класс-одиночку.
SingleObject.java
Шаг 2
Получите уникальный объект из класса-одиночки.
SingletonPatternDemo.java
Шаг 3
выполнение программы, вывод результата:
Hello World!
Несколько подходов к реализации шаблона одиночка
Существует несколько способов реализации шаблона одиночка, как показано ниже:
1. Ленивый режим, небезопасный для потоков
Ленивая инициализация:Да
Потокобезопасно:нет
Сложность реализации:низкая
Описание:Это самая базовая реализация. Главная проблема этой реализации в том, что она не поддерживает многопоточность. Поскольку здесь нет synchronized-блокировки, строго говоря, это не паттерн «одиночка».
Этот подход имеет очевидную ленивую загрузку, не требует потокобезопасности и не может корректно работать в многопоточной среде.
Пример
Несколько представленных ниже методов реализации поддерживают многопоточность, но различаются по производительности.
2. Ленивый режим, потокобезопасный
Ленивая инициализация:Да
Является ли потокобезопасным:Да
Сложность реализации:низкая
Описание:Этот подход обеспечивает хорошую ленивую загрузку и может хорошо работать при многопоточности, но его эффективность очень низкая, и синхронизация не нужна в 99% случаев.
Преимущества: инициализируется только при первом вызове, что позволяет избежать напрасного расхода памяти.
Недостатки: нужно добавить synchronized, чтобы обеспечить поведение синглтона, но блокировка влияет на эффективность.
Производительность getInstance() не критична для приложения (этот метод используется нечасто).
Пример
3. Ранняя инициализация
Является ли инициализация ленивой:нет
Является ли потокобезопасным:Да
Сложность реализации:низкая
Описание:Этот подход довольно распространён, но он может легко создавать мусорные объекты.
Преимущества: отсутствие блокировок, повышается эффективность выполнения.
Недостатки: инициализируется во время загрузки класса, расходуется память.
Он позволяет избежать проблем синхронизации многопоточности благодаря механизму загрузчика классов. Однако экземпляр создаётся при загрузке класса. Хотя существует много причин, вызывающих загрузку класса, в шаблоне singleton большинство из них — вызовы метода getInstance, но нельзя подтвердить, что нет других способов (или других статических методов), вызывающих загрузку класса. В таком случае инициализация экземпляра, очевидно, не достигает эффекта ленивой загрузки.
Пример
4. Блокировка с двойной проверкой / double-checked locking (DCL, то есть блокировка с двойной проверкой)
Версия JDK:Начиная с JDK 1.5
Ленивая инициализация:Да
Является ли потокобезопасным:Да
Сложность реализации:Более сложная
Описание:В этом подходе используется механизм двойной блокировки, который безопасен и обеспечивает высокую производительность при многопоточности.
Производительность getInstance() критична для приложения.
Пример
5. Реестр/Статический внутренний класс
Ленивая инициализация:Да
Является ли потокобезопасным:Да
Сложность реализации:Средняя
Описание:Этот подход позволяет достичь того же эффекта, что и блокировка с двойной проверкой, но его проще реализовать. Для ленивой инициализации статических полей следует использовать этот подход вместо блокировки с двойной проверкой. Этот подход применим только к статическим полям; блокировку с двойной проверкой можно использовать, когда поля экземпляра требуют ленивой инициализации.
Этот подход также использует механизм загрузчика классов, чтобы гарантировать, что только один поток инициализирует экземпляр. Он отличается от подхода 3 тем, что: в подходе 3, как только класс Singleton загружается, экземпляр будет создан (без достижения ленивой загрузки), тогда как в этом подходе после загрузки класса Singleton экземпляр не обязательно инициализируется. Поскольку класс SingletonHolder активно не используется, только когда метод getInstance явно вызывается, класс SingletonHolder явно загружается, и тем самым создаётся экземпляр. Представьте, что если создание экземпляра потребляет много ресурсов, вы можете захотеть, чтобы он загружался лениво. С другой стороны, вы можете не захотеть, чтобы он создавался при загрузке класса Singleton, потому что нет гарантии, что класс Singleton не будет активно использоваться и загружаться где-то ещё. В этом случае создание экземпляра в тот момент, очевидно, неуместно. В этом плане этот подход гораздо разумнее, чем подход 3.
Пример
6. Перечисление
Версия JDK:Начиная с JDK 1.5
Является ли инициализация ленивой:нет
Является ли потокобезопасным:Да
Сложность реализации:низкая
Описание:Этот подход к реализации не получил широкого распространения, но он является лучшим способом реализации шаблона одиночка. Он более лаконичен, автоматически поддерживает механизм сериализации и абсолютно предотвращает многократное создание экземпляров.
Этот подход отстаивает Джош Блох, автор Effective Java. Он не только позволяет избежать проблем синхронизации при многопоточности, но и автоматически поддерживает механизм сериализации, предотвращает повторное создание новых объектов при десериализации и абсолютно исключает множественное создание экземпляров. Однако, поскольку функция enum была добавлена только после JDK 1.5, такой способ написания неизбежно кажется непривычным, и на практике он используется редко.
С его помощью нельзя вызвать приватный конструктор через атаку с использованием рефлексии.
Пример
Из опыта:В общем случае не рекомендуется использовать 1-й и 2-й ленивые подходы; рекомендуется 3-й нетерпеливый подход. Только когда необходимо явно реализовать ленивую загрузку, следует использовать 5-й подход с регистрацией. Если при создании объектов задействована десериализация, можно попробовать 6-й подход с enum. Если есть другие особые требования, можно рассмотреть 4-й подход с двойной проверкой блокировки.
Другие расширения