8

Как эффективно реализовать шаблон одиночки в Java? [закрыто]

1

Проблема: Вопрос о том, как эффективно реализовать шаблон проектирования Singleton на Java, был закрыт. Он требует большей концентрации и не принимает ответы.

Если вы хотите улучшить этот вопрос, обновите его, сосредоточив внимание только на одной проблеме, отредактировав пост. Вопрос был закрыт 6 лет назад.

5 ответ(ов)

2

В зависимости от контекста использования существует несколько "правильных" ответов.

Начиная с 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() гарантирует, что будет возвращён единственный экземпляр, даже если объект был сериализован во время предыдущего запуска вашей программы.

0

Обратите внимание на модификатор volatile в этом коде. 😃 Он важен, потому что без него другие потоки не имеют гарантии, что увидят изменения значения переменной bar согласно JMM (Java Memory Model). Синхронизация сама по себе не решает эту проблему — она просто обеспечивает последовательный доступ к блоку кода.

Ответ @Bno подробно описывает подход, рекомендуемый Биллом Пью (FindBugs), и может считаться более эффективным. Обязательно прочитайте его ответ и поставьте ему лайк.

0

Вот три разных подхода для реализации паттерна Singleton в Java:

  1. Enum
/**
 * Пример паттерна Singleton с использованием Java Enum
 */
public enum EasySingleton {
    INSTANCE;
}

Использование Enum для реализации Singleton является наиболее простым и безопасным способом. Он автоматически обеспечивает сериализацию и защиту от создания дополнительных экземпляров через механизм Java.

  1. Двойная проверка блокировки / ленивое инициализирование
/**
 * Пример паттерна 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;
    }
}

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

  1. Статический фабричный метод
/**
 * Пример паттерна Singleton с использованием статического фабричного метода
 */
public class Singleton {
    // Инициализировано во время загрузки класса
    private static final Singleton INSTANCE = new Singleton();

    // Для предотвращения создания другого экземпляра 'Singleton'
    private Singleton() {}

    public static Singleton getSingleton() {
        return INSTANCE;
    }
}

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

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

0

Версия 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 инициализируется. Это решение также считается более элегантным и эффективным.

0

Если вам не нужно ленивое создание экземпляра (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 или создать реестр для всех ваших синглтонов.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь