Синглтоны в Java

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. Enum Singleton

Двигаясь вперед, давайте не будем обсуждать еще один интересный подход - использование перечислений:

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 }

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

4. Использование

Чтобы использовать наш ClassSingleton , нам просто нужно получить экземпляр статически:

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

Что касается EnumSingleton , мы можем использовать его как любое другое Java Enum:

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 == null будет истинным для обоих потоков, и в результате будут созданы два экземпляра ClassSingleton .

Синхронизация может существенно повлиять на производительность. Если этот код вызывается часто, мы должны ускорить его, используя различные методы, такие как ленивая инициализация или блокировка с двойной проверкой (имейте в виду, что это может работать не так, как ожидалось, из-за оптимизации компилятора). Мы можем увидеть более подробную информацию в нашем руководстве «Двойная проверка блокировки с помощью синглтона».

Несколько экземпляров

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

  1. Синглтон должен быть уникальным для каждой JVM. Это может быть проблемой для распределенных систем или систем, внутреннее устройство которых основано на распределенных технологиях.
  2. Каждый загрузчик классов может загрузить свою версию синглтона.
  3. Синглтон может быть обработан сборщиком мусора, если никто не ссылается на него. Эта проблема не приводит к наличию нескольких экземпляров синглтона одновременно, но при повторном создании экземпляр может отличаться от своей предыдущей версии.

6. Заключение

В этом кратком руководстве мы сосредоточились на том, как реализовать шаблон Singleton, используя только ядро ​​Java, и как обеспечить его согласованность и как использовать эти реализации.

Полную реализацию этих примеров можно найти на GitHub.