Статья

Паттерн Singleton в Java

В этом кратком руководстве мы обсудим два наиболее популярных способа реализации синглтонов на в Java.

Синглтон на основе класса

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

  • статическое поле, содержащее его единственный экземпляр

  • статический фабричный метод для получения экземпляра
В коде добавим свойство msg для последующего использования. Итак, наша реализация будет выглядеть следующим образом:
public final class MyClassSingleton {

    private static MyClassSingleton INSTANCE;
    private String msg = "Info class";
    
    private MyClassSingleton() {        
    }
    
    public static MyClassSingleton getInstance() {
        if(INSTANCE == null) {
            INSTANCE = new ClassSingleton();
        }
        
        return INSTANCE;
    }
}
Хотя это распространенный подход, важно отметить, что он может быть проблематичным в сценариях многопоточности, что является основной причиной использования синглтонов.

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

Enum синглтон

Двигаясь вперед, давайте обсудим другой интересный подход, который заключается в использовании перечислений:
public enum EnumSingleton {
    
    INSTANCE("Сlass enum info"); 
 
    private String msg;
 
    private EnumSingleton(String msg) {
        this.msg = msg;
    }
 
    public EnumSingleton getInstance() {
        return INSTANCE;
    }
}
Этот подход обеспечивает сериализацию и потокобезопасность, гарантируемые самой реализацией enum, которая внутренне гарантирует, что доступен только один экземпляр, исправляя проблемы, указанные в реализации на основе классов.
Чтобы использовать наш MyClassSingleton, нам просто нужно получить статический экземпляр:
MyClassSingleton classSingleton1 = MyClassSingleton.getInstance();

System.out.println(classSingleton1.getMsg()); // "Info class";

MyClassSingleton classSingleton2 = MyClassSingleton.getInstance();
classSingleton2.setInfo("New class message");

System.out.println(classSingleton1.getMsg()); //New class message
System.out.println(classSingleton2.getMsg()); //New class message
Что касается EnumSingleton, мы можем использовать его как любое другое перечисление Java:
EnumSingleton enumSingleton1 = EnumSingleton.INSTANCE.getInstance();

System.out.println(enumSingleton1.getInfo()); //Сlass enum info

EnumSingleton enumSingleton2 = EnumSingleton.INSTANCE.getInstance();
enumSingleton2.setInfo("New info");

System.out.println(enumSingleton1.getInfo()); // New info
System.out.println(enumSingleton2.getInfo()); // New info
Синглтон - это обманчиво простой шаблон проектирования, и существует несколько распространенных ошибок, которые программист может совершить при создании синглтона.

Мы можем выделить два типа проблем, связанных с синглтонами:
  1. экзистенциальный (нужен ли нам синглтон?)
  2. имплементационный (правильно ли мы это реализовали?)

Экзистенциальные проблемы

Концептуально синглтон - это своего рода глобальная переменная. В общем, мы знаем, что глобальных переменных следует избегать, особенно если их состояния изменчивы.

Мы не говорим, что нам никогда не следует использовать синглтоны; однако мы говорим, что могут быть более эффективные способы организации нашего кода.

Если реализация метода зависит от объекта singleton, почему бы не передать его в качестве параметра? В этом случае мы явно показываем, от чего зависит метод. В результате мы можем легко имитировать эти зависимости (при необходимости) при выполнении тестирования.
Например, синглтоны часто используются для получения данных конфигурации приложения (т.е. подключения к репозиторию). Если они используются как глобальные объекты, становится трудно выбрать конфигурацию для тестовой среды.

Поэтому, когда мы запускаем тесты, производственная база данных портится тестовыми данными, что вряд ли приемлемо.

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

Проблемы реализации

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

Синхронизация

Реализация с private конструктором, которую мы представили выше, не является потокобезопасной. Это хорошо работает в однопоточной среде, но в многопоточной мы должны использовать технику синхронизации, чтобы гарантировать атомарность операции:
public synchronized static MyClassSingleton getInstance() {
    if (INSTANCE == null) {
        INSTANCE = new MyClassSingleton();
    }
    return INSTANCE;
}
Обратите внимание на ключевое слово synchronized в объявлении метода. Тело метода содержит несколько операций (сравнение, создание экземпляра и возврат).

При отсутствии синхронизации существует вероятность того, что два потока чередуют свои выполнения таким образом, что выражение INSTANCE == null принимает значение true для обоих потоков, и в результате создаются два экземпляра MyClassSingleton.
Синхронизация может существенно повлиять на производительность. Если этот код вызывается часто, нам следует ускорить его, используя различные методы, такие как отложенная инициализация или блокировка с двойной проверкой (имейте в виду, что это может работать не так, как ожидалось, из-за оптимизации компилятора).

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

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

  • Каждый загрузчик классов может загружать свою версию синглтона.

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

Заключение

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