Как эффективно реализовать шаблон одиночки в Java? [закрыто]
Проблема: Вопрос о том, как эффективно реализовать шаблон проектирования Singleton на Java, был закрыт. Он требует большей концентрации и не принимает ответы.
Если вы хотите улучшить этот вопрос, обновите его, сосредоточив внимание только на одной проблеме, отредактировав пост. Вопрос был закрыт 6 лет назад.
5 ответ(ов)
В зависимости от контекста использования существует несколько "правильных" ответов.
Начиная с Java 5, лучший способ реализовать одиночку (Singleton) — это использовать перечисления (enum):
public enum Foo {
INSTANCE;
}
Для версий Java до 5, самый простой способ выглядит следующим образом:
public final class Foo {
private static final Foo INSTANCE = new Foo();
private Foo() {
if (INSTANCE != null) {
throw new IllegalStateException("Уже инициализирован");
}
}
public static Foo getInstance() {
return INSTANCE;
}
public Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException("Невозможно клонировать экземпляр этого класса");
}
}
Давайте разберём код. Во-первых, мы хотим, чтобы класс был окончательным (final). В данном случае я использую ключевое слово final
, чтобы сообщить пользователям, что этот класс не может быть унаследован. Затем вам нужно сделать конструктор закрытым (private), чтобы предотвратить создание экземпляров класса Foo
. Генерация исключения в конструкторе не позволяет пользователям использовать рефлексию для создания второго экземпляра Foo
. Далее создаем private static final Foo
поле для хранения единственного экземпляра, а также метод public static Foo getInstance()
, который будет его возвращать. Спецификация Java гарантирует, что конструктор будет вызван только при первом обращении к классу.
Если у вас есть очень большой объект или тяжелая кодовая конструкция и также есть другие доступные статические методы или поля, которые могут быть использованы до того, как экземпляр будет нужен, тогда и только тогда вам следует использовать ленивую инициализацию.
Вы можете использовать private static class
для загрузки экземпляра. Код будет выглядеть так:
public final class Foo {
private static class FooLoader {
private static final Foo INSTANCE = new Foo();
}
private Foo() {
if (FooLoader.INSTANCE != null) {
throw new IllegalStateException("Уже инициализирован");
}
}
public static Foo getInstance() {
return FooLoader.INSTANCE;
}
}
Поскольку строка private static final Foo INSTANCE = new Foo();
выполняется только тогда, когда класс FooLoader
фактически используется, это обеспечивает ленивую инициализацию и гарантирует потокобезопасность.
Если вы также хотите иметь возможность сериализовать ваш объект, необходимо убедиться, что десериализация не создаст копию.
public final class Foo implements Serializable {
private static final long serialVersionUID = 1L;
private static class FooLoader {
private static final Foo INSTANCE = new Foo();
}
private Foo() {
if (FooLoader.INSTANCE != null) {
throw new IllegalStateException("Уже инициализирован");
}
}
public static Foo getInstance() {
return FooLoader.INSTANCE;
}
@SuppressWarnings("unused")
private Foo readResolve() {
return FooLoader.INSTANCE;
}
}
Метод readResolve()
гарантирует, что будет возвращён единственный экземпляр, даже если объект был сериализован во время предыдущего запуска вашей программы.
Обратите внимание на модификатор volatile
в этом коде. 😃 Он важен, потому что без него другие потоки не имеют гарантии, что увидят изменения значения переменной bar
согласно JMM (Java Memory Model). Синхронизация сама по себе не решает эту проблему — она просто обеспечивает последовательный доступ к блоку кода.
Ответ @Bno подробно описывает подход, рекомендуемый Биллом Пью (FindBugs), и может считаться более эффективным. Обязательно прочитайте его ответ и поставьте ему лайк.
Вот три разных подхода для реализации паттерна Singleton в Java:
- Enum
/**
* Пример паттерна Singleton с использованием Java Enum
*/
public enum EasySingleton {
INSTANCE;
}
Использование Enum
для реализации Singleton является наиболее простым и безопасным способом. Он автоматически обеспечивает сериализацию и защиту от создания дополнительных экземпляров через механизм Java.
- Двойная проверка блокировки / ленивое инициализирование
/**
* Пример паттерна Singleton с использованием двойной проверки блокировки
*/
public class DoubleCheckedLockingSingleton {
private static volatile DoubleCheckedLockingSingleton INSTANCE;
private DoubleCheckedLockingSingleton() {}
public static DoubleCheckedLockingSingleton getInstance() {
if(INSTANCE == null) {
synchronized(DoubleCheckedLockingSingleton.class) {
// Двойная проверка экземпляра Singleton
if(INSTANCE == null) {
INSTANCE = new DoubleCheckedLockingSingleton();
}
}
}
return INSTANCE;
}
}
Этот метод использует двойную проверку блокировки, что позволяет избежать лишней синхронизации после того, как экземпляр был создан, тем самым повышая производительность.
- Статический фабричный метод
/**
* Пример паттерна Singleton с использованием статического фабричного метода
*/
public class Singleton {
// Инициализировано во время загрузки класса
private static final Singleton INSTANCE = new Singleton();
// Для предотвращения создания другого экземпляра 'Singleton'
private Singleton() {}
public static Singleton getSingleton() {
return INSTANCE;
}
}
Этот подход инициализирует экземпляр во время загрузки класса, что исключает возможность создания дополнительных экземпляров и обеспечивает потокобезопасность при доступе к экземпляру.
Каждый из этих методов имеет свои преимущества, и выбор подхода может зависеть от конкретных требований вашего приложения.
Версия 1:
public class MySingleton {
private static MySingleton instance = null;
private MySingleton() {}
public static synchronized MySingleton getInstance() {
if (instance == null) {
instance = new MySingleton();
}
return instance;
}
}
Эта реализация паттерна Singleton использует ленивую инициализацию и безопасна для потоков, однако обладает недостатком производительности из-за использования synchronized
. Каждый вызов метода getInstance()
будет блокировать поток до тех пор, пока объект не будет создан, что может негативно сказаться на производительности в многопоточных приложениях.
Версия 2:
public class MySingleton {
private MySingleton() {}
private static class MySingletonHolder {
public static final MySingleton instance = new MySingleton();
}
public static MySingleton getInstance() {
return MySingletonHolder.instance;
}
}
Эта версия также реализует ленивую инициализацию и является потокобезопасной, но при этом не требует блокировок, что значительно улучшает производительность. Объект MySingleton
создается только при первом обращении к getInstance()
, когда класс MySingletonHolder
инициализируется. Это решение также считается более элегантным и эффективным.
Если вам не нужно ленивое создание экземпляра (lazy loading), вы можете использовать следующий код для реализации синглтона:
public class Singleton {
private final static Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() { return Singleton.INSTANCE; }
protected Object clone() {
throw new CloneNotSupportedException();
}
}
Если же вы хотите реализовать ленивое создание экземпляра и обеспечить потокобезопасность вашего синглтона, вы можете применить паттерн двойной проверки (double-checking):
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (null == instance) {
synchronized (Singleton.class) {
if (null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
protected Object clone() {
throw new CloneNotSupportedException();
}
}
Однако следует иметь в виду, что паттерн двойной проверки не гарантирует свою работоспособность на всех компиляторах из-за возможных проблем, о которых мне ничего не известно. Поэтому, вы также можете рассмотреть возможность синхронизации всего метода getInstance или создать реестр для всех ваших синглтонов.
Как реализовать паттерн проектирования Singleton?
Примеры паттернов проектирования GoF в ядре библиотек Java
Как создать утечку памяти в Java?
Инициализация ArrayList в одну строчку
Почему нет ConcurrentHashSet, если есть ConcurrentHashMap?