Как стать автором
Обновить
1091.33
OTUS
Цифровые навыки от ведущих экспертов

Паттерны Singleton и Multiton в Java: когда и какой лучше?

Уровень сложностиПростой
Время на прочтение6 мин
Количество просмотров5.5K

Привет, Хабр!

Паттерн Singleton гарантирует существование лишь одного экземпляра класса и предоставляет к нему глобальную точку доступа. Этот паттерн стал почти синонимом чистоты кода в многих сценариях работы с Java, где требуется строго один экземпляр объекта. Но не менее интересный и гибкий паттерн - это Multiton. Менее известный, но не менее мощный, он позволяет создавать множество экземпляров класса и контролировать их число и жизненный цикл через предопределенные ключи.

В этой статье мы рассмотрим эти паттерны и их различия.

Singleton

Суть Singleton заключается не просто в ограничении инстанцирования класса одним объектом, но и в предоставлении универсальной точки доступа к этому экземпляру.

Пн помогает контролировать доступ к ресурсам, которые по своей природе должны быть уникальными. Т.е: доступ к БД, файловой системе или какому-либо общему ресурсу, который требует строгого контроля над своим состоянием и доступностью.

Singleton работает по принципу отложенной инициализации — экземпляр класса создается только тогда, когда он впервые нужен. Т.е так ресурсы используются экономно и эффективно, поскольку инициализация происходит только по требованию.

С точки зрения реализации, основными моментами Singleton являются:

  • Приватный конструктор, который предотвращает прямое создание объекта класса.

  • Статическая переменная, которая хранит экземпляр Singleton класса.

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

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

Примеры кода

Рассмотрим пять классических вариантов:

Самый базовый вариант Singleton включает в себя приватный конструктор и статический метод для получения экземпляра:

public class ClassicSingleton {
    private static ClassicSingleton instance;

    private ClassicSingleton() {}

    public static ClassicSingleton getInstance() {
        if (instance == null) {
            instance = new ClassicSingleton();
        }
        return instance;
    }
}

Для обеспечения потокобезопасности в многопоточной среде используется синхронизация:

public class ThreadSafeSingleton {
    private static ThreadSafeSingleton instance;

    private ThreadSafeSingleton() {}

    public static synchronized ThreadSafeSingleton getInstance() {
        if (instance == null) {
            instance = new ThreadSafeSingleton();
        }
        return instance;
    }
}

Lazy Holder использует вложенный статический класс для отложенной инициализации экземпляра:

public class LazyHolderSingleton {
    private LazyHolderSingleton() {}

    private static class LazyHolder {
        static final LazyHolderSingleton INSTANCE = new LazyHolderSingleton();
    }

    public static LazyHolderSingleton getInstance() {
        return LazyHolder.INSTANCE;
    }
}

Использование перечислений для реализации Singleton гарантирует противодействие проблемам сериализации:

public enum EnumSingleton {
    INSTANCE;

    public void someMethod() {
        // Реализация метода
        System.out.println("Log message: " + message);
    }
}

Double-checked locking для ленивой инициализации уменьшает затраты на синхронизацию, проверяя экземпляр дважды:

public class DoubleCheckedLockingSingleton {
    private static volatile DoubleCheckedLockingSingleton instance;

    private DoubleCheckedLockingSingleton() {}

    public static DoubleCheckedLockingSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckedLockingSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckedLockingSingleton();
                }
            }
        }
        return instance;
    }
}

Реализуем логирование и управление подключением к БД

Системы логирования - это классический пример использования Singleton, поскольку обычно требуется единственный экземпляр логгера на всё приложение. Т.е с паттерном все части приложения используют один и тот же экземпляр логгера:

public class LoggerSingleton {
    private static LoggerSingleton instance;
    private LoggerSingleton() {}

    public static synchronized LoggerSingleton getInstance() {
        if (instance == null) {
            instance = new LoggerSingleton();
        }
        return instance;
    }

    public void log(String message) {
        // примитивная реализация записи сообщения в лог
        System.out.println(System.currentTimeMillis() + ": " + message);
    }
}

Singleton также часто используется для управления подключениями к БД, гарантируя, что вся система использует единственное подключение, или управляет пулом подключений через олин экземпляр:

public class DatabaseConnectionSingleton {
    private static DatabaseConnectionSingleton instance;
    private Connection connection;

    private DatabaseConnectionSingleton() {
        try {
            // инициализация подключения к БД
            this.connection = DriverManager.getConnection("jdbc:example:database:url", "user", "password");
        } catch (SQLException e) {
            // обработка исключения
        }
    }

    public static DatabaseConnectionSingleton getInstance() {
        if (instance == null) {
            synchronized (DatabaseConnectionSingleton.class) {
                if (instance == null) {
                    instance = new DatabaseConnectionSingleton();
                }
            }
        }
        return instance;
    }

    public Connection getConnection() {
        return connection;
    }
}

Multiton

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

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

Примеры

Пул подключения к БД:

public class DatabaseConnection {
    private static final Map<String, DatabaseConnection> instances = new HashMap<>();

    private DatabaseConnection() {
        // инициализация подключения к базе данных
    }

    public static synchronized DatabaseConnection getInstance(String dbName) {
        if (!instances.containsKey(dbName)) {
            instances.put(dbName, new DatabaseConnection());
        }
        return instances.get(dbName);
    }
}

Кэширование объекта:

public class ObjectCache {
    private static final Map<String, Object> cache = new HashMap<>();

    private ObjectCache() {
        // инициализация кэша
    }

    public static synchronized Object getInstance(String key) {
        if (!cache.containsKey(key)) {
            cache.put(key, new Object());
        }
        return cache.get(key);
    }
}

Лог разных модулей приложения:

public class Logger {
    private static final Map<String, Logger> loggers = new HashMap<>();
    private String moduleName;

    private Logger(String moduleName) {
        this.moduleName = moduleName;
        // инициализация логгера
    }

    public static synchronized Logger getInstance(String moduleName) {
        if (!loggers.containsKey(moduleName)) {
            loggers.put(moduleName, new Logger(moduleName));
        }
        return loggers.get(moduleName);
    }

    public void log(String message) {
        System.out.println("[" + moduleName + "] " + message);
    }

Сравним эти паттерны

Singleton:

  1. Описание: гарантирует, что для класса существует только один экземпляр, и предоставляет глобальную точку доступа к нему.

  2. Управление экземпляром: один экземпляр класса.

  3. Ограничение: нет возможности создавать несколько экземпляров класса.

  4. Идентификация: единственный экземпляр идентифицируется по статическому методу или переменной.

  5. Применение: используется для доступа к общим ресурсам, кэширования объектов, логирования и т.д.

Multiton:

  1. Описание: похож на Singleton, но позволяет создавать и управлять множеством экземпляров класса с уникальными ключами.

  2. Управление экземпляром: множество экземпляров класса, каждый из которых идентифицируется уникальным ключом.

  3. Ограничение: ограниченное кол-воэкземпляров, определенных по ключам.

  4. Идентификация: каждый экземпляр идентифицируется уникальным ключом.

  5. Применение: спользуется для управления пулами ресурсов, кэширования с ограниченным размером, управления соединениями к БД и т.д.

Для наглядности сделал табличку:

Параметр

Singleton

Multiton

Управление

Один экземпляр класса

Множество экземпляров по ключам

Ограничение

Один экземпляр

Ограниченное количество

Идентификация

По статическому методу или переменной

По уникальному ключу

Применение

Общие ресурсы, кэширование, логирование

Пулы ресурсов, кэширование с ограничением, управление соединениями к БД и т.д.


Итак, если задача требует существования только одного экземпляра класса в приложении, например, для доступа к общим ресурсам или управления глобальными настройками, то Singleton - хороший выбор. Если нужно иметь несколько экземпляров класса с различными характеристиками или параметрами, например, для управления пулами ресурсов с ограниченным размером или для работы с разными источниками данных, то Multiton конечно будет намного лучше.

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

Теги:
Хабы:
Всего голосов 12: ↑8 и ↓4+6
Комментарии9

Публикации

Информация

Сайт
otus.ru
Дата регистрации
Дата основания
Численность
101–200 человек
Местоположение
Россия
Представитель
OTUS