Синглтоны в Java

Синглтоны на Яве

1. Вступление

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

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

Самый популярный подход - реализовать Singleton, создав обычный класс и убедившись, что он имеет:

  • Частный конструктор

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

  • Статический фабричный метод для получения экземпляра

Мы также добавим свойство 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
}

Хотя это распространенный подход, важно отметить, что этоcan be problematic in multithreading scenarios, что является основной причиной использования синглтонов.

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

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. Вопросы реализации

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

Synchronization Реализация с частным конструктором, которую мы представили выше, не является потокобезопасной: она хорошо работает в однопоточной среде, но в многопоточной среде мы должны использовать технику синхронизации, чтобы гарантировать атомарность операция:

public synchronized static ClassSingleton getInstance() {
    if (INSTANCE == null) {
        INSTANCE = new ClassSingleton();
    }
    return INSTANCE;
}

Обратите внимание на ключевое словоsynchronized в объявлении метода. В теле метода есть несколько операций (сравнение, создание экземпляра и возврат).

В отсутствие синхронизации существует вероятность того, что два потока чередуют свои исполнения таким образом, что выражениеINSTANCE == null оценивается какtrue  для обоих потоков и, как результат, два экземпляраClassSingleton создать.

Synchronization может существенно повлиять на производительность. Если этот код вызывается часто, мы должны ускорить его, используя различные методы, такие какlazy initialization илиdouble-checked locking (имейте в виду, что это может не работать должным образом из-за оптимизации компилятора). Мы можем увидеть более подробную информацию в нашем руководстве «https://www.example.com/java-singleton-double-checked-locking[Double-Checked Locking with Singleton]».

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

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

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

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

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

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

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