1. введение
В этой краткой статье мы обсудим два наиболее популярных способа реализации синглетов в простой Java.
2. Синглтон на основе классов
Самый популярный подход-реализовать синглтон, создав обычный класс и убедившись, что он имеет:
- Частный конструктор
- Статическое поле, содержащее его единственный экземпляр
- Статический фабричный метод для получения экземпляра
Мы также добавим свойство info, только для последующего использования. Итак, наша реализация будет выглядеть следующим образом:
public final class ClassSingleton { private static ClassSingleton INSTANCE; private String info = "Initial info class"; private ClassSingleton() { } public static ClassSingleton getInstance() { if(INSTANCE == null) { INSTANCE = new ClassSingleton(); } return INSTANCE; } // getters and setters }
Хотя это распространенный подход, важно отметить , что он может быть проблематичным в многопоточных сценариях , что является основной причиной использования синглетов.
Проще говоря, это может привести к более чем одному случаю, нарушающему основной принцип шаблона. Хотя существует множество решений этой проблемы, наш следующий подход решает эти проблемы на корневом уровне.
3. Перечисление синглтона
Двигаясь вперед, давайте не будем обсуждать еще один интересный подход – использование перечислений:
public enum EnumSingleton { INSTANCE("Initial class info"); private String info; private EnumSingleton(String info) { this.info = info; } public EnumSingleton getInstance() { return INSTANCE; } // getters and setters }
Этот подход имеет сериализацию и потокобезопасность, гарантированные самой реализацией перечисления, которая внутренне гарантирует, что доступен только один экземпляр, исправляя проблемы, указанные в реализации на основе классов.
4. Использование
Чтобы использовать наш класс Singleton , нам просто нужно получить экземпляр статически:
ClassSingleton classSingleton1 = ClassSingleton.getInstance(); System.out.println(classSingleton1.getInfo()); //Initial class info ClassSingleton classSingleton2 = ClassSingleton.getInstance(); classSingleton2.setInfo("New class info"); System.out.println(classSingleton1.getInfo()); //New class info System.out.println(classSingleton2.getInfo()); //New class info
Что касается синглтона Enum , мы можем использовать его, как и любое другое перечисление Java:
EnumSingleton enumSingleton1 = EnumSingleton.INSTANCE.getInstance(); System.out.println(enumSingleton1.getInfo()); //Initial enum info EnumSingleton enumSingleton2 = EnumSingleton.INSTANCE.getInstance(); enumSingleton2.setInfo("New enum info"); System.out.println(enumSingleton1.getInfo()); // New enum info System.out.println(enumSingleton2.getInfo()); // New enum info
5. Общие подводные камни
Синглтон-это обманчиво простой шаблон проектирования, и существует несколько распространенных ошибок, которые программист может совершить при создании синглтона.
Мы различаем два типа проблем с синглетами:
- экзистенциальный (нужен ли нам синглтон?)
- реализация (правильно ли мы ее реализуем?)
5.1. Экзистенциальные проблемы
Концептуально синглтон-это своего рода глобальная переменная. В общем, мы знаем, что глобальных переменных следует избегать, особенно если их состояния изменчивы.
Мы не говорим, что никогда не должны использовать синглеты. Тем не менее, мы говорим, что могут быть более эффективные способы организации нашего кода.
Если реализация метода зависит от одноэлементного объекта, почему бы не передать его в качестве параметра? В этом случае мы явно покажем, от чего зависит метод. Как следствие, мы можем легко издеваться над этими зависимостями (при необходимости) при выполнении тестирования.
Например, синглеты часто используются для охвата данных конфигурации приложения (т. Е. Подключения к репозиторию). Если они используются в качестве глобальных объектов, становится трудно выбрать конфигурацию для тестовой среды.
Поэтому, когда мы запускаем тесты, производственная база данных портится тестовыми данными, что вряд ли приемлемо.
Если нам нужен синглтон, мы могли бы рассмотреть возможность делегирования его экземпляра другому классу — своего рода фабрике, — которая должна позаботиться о том, чтобы в игре был только один экземпляр синглтона.
5.2. Вопросы реализации
Несмотря на то, что синглеты кажутся довольно простыми, их реализация может страдать от различных проблем. Все это приводит к тому, что у нас может оказаться больше, чем один экземпляр класса.
Синхронизация
Реализация с частным конструктором, которую мы представили выше, не является потокобезопасной: она хорошо работает в однопоточной среде, но в многопоточной среде мы должны использовать метод синхронизации, чтобы гарантировать атомарность операции:
public synchronized static ClassSingleton getInstance() { if (INSTANCE == null) { INSTANCE = new ClassSingleton(); } return INSTANCE; }
Обратите внимание на ключевое слово synchronized в объявлении метода. Тело метода содержит несколько операций (сравнение, создание экземпляра и возврат).
В отсутствие синхронизации существует вероятность того, что два потока чередуют свои выполнения таким образом, что выражение INSTANCE вычисляется как true для обоих потоков и, в результате, создаются два экземпляра ClassSingleton .
Синхронизация может существенно повлиять на производительность. Если этот код часто вызывается, мы должны ускорить его, используя различные методы, такие как ленивая инициализация или блокировка с двойной проверкой (имейте в виду, что это может работать не так, как ожидалось, из-за оптимизации компилятора). Мы можем увидеть более подробную информацию в нашем уроке ” Двойная блокировка с помощью синглтона “.
Несколько экземпляров Есть несколько других проблем с синглетонами, связанных с самой JVM, которые могут привести к тому, что мы получим несколько экземпляров синглета. Эти проблемы довольно тонкие, и мы дадим краткое описание для каждого из них:
- Предполагается, что синглтон уникален для каждой JVM. Это может быть проблемой для распределенных систем или систем, внутренние компоненты которых основаны на распределенных технологиях.
- Каждый загрузчик классов может загрузить свою версию синглтона.
- Синглтон может быть собран из мусора, если никто не имеет на него ссылки. Эта проблема не приводит к наличию нескольких одноэлементных экземпляров одновременно, но при повторном создании экземпляр может отличаться от своей предыдущей версии.
6. Заключение
В этом кратком руководстве мы сосредоточились на том, как реализовать шаблон одноэлементного кода, используя только основную Java, и как убедиться, что он согласован, и как использовать эти реализации.
Полную реализацию этих примеров можно найти на GitHub .