Singleton в Java

Я просто добрался для чтения следующего кода где-нибудь:

public class SingletonObjectDemo {

    private static SingletonObjectDemo singletonObject;
    // Note that the constructor is private
    private SingletonObjectDemo() {
        // Optional Code
    }
    public static SingletonObjectDemo getSingletonObject() {
        if (singletonObject == null) {
            singletonObject = new SingletonObjectDemo();
       }
       return singletonObject;
    }
}

Я должен знать то, что является потребностью этой части:

if (singletonObject == null) {
    singletonObject = new SingletonObjectDemo();
}

Что, если мы не используем эту часть кода? Все еще была бы единственная копия SingletonObjectDemo, почему нам нужен этот код затем?

5
задан Aza 13 April 2013 в 00:15
поделиться

7 ответов

Этот класс имеет поле SingletonObjectDemo singletonObject , которое содержит экземпляр singleton. Теперь есть две возможные стратегии -

1 - Вы активно инициализируете объект с помощью объявления -

private static SingletonObjectDemo singletonObject = new SingletonObjectDemo();

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

2 - Вы выполняете ленивую инициализацию объекта, то есть инициализируете его при первом вызове getSingletonObject () -

// note that this initializes the object to null by default
private static SingletonObjectDemo singletonObject;

...

if (singletonObject == null) {
        singletonObject = new SingletonObjectDemo();
    }

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

5
ответ дан 18 December 2019 в 07:08
поделиться

What если мы не будем использовать эту часть кода? По-прежнему будет одна копия SingletonObjectDemo, зачем нам тогда этот код?

Идея состоит в том, чтобы лениво загружать синглтон, то есть загружать экземпляр только тогда, когда это действительно необходимо . Почему вы хотите это сделать? Что ж, Боб Ли довольно хорошо резюмирует это в Ленивая загрузка синглтонов :

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

Но реализация, которую вы показываете, не работает, она не является потокобезопасной, и два параллельных потока могут фактически создать два экземпляра. лучший способ сделать вашу ленивую загружаемую singleton thread безопасную - это использовать идиому Initialization on Demand Holder (IODH) , которая очень проста и имеет нулевую синхронизацию накладные расходы. Цитата «Эффективная Java», Правило 71: Разумно используйте ленивую инициализацию (курсив не мой):

Если вам нужно использовать ленивую инициализацию для повышения производительности на статическое поле, используйте lazy идиома класса держателя инициализации . Эта идиома (также известная как идиома класса держателя инициализации по требованию ) использует гарантию того, что класс не будет инициализирован, пока он используется [JLS, 12.4.1].Вот как это выглядит:

 // Идиома класса держателя ленивой инициализации для статических полей
частный статический класс FieldHolder {
статическое конечное поле FieldType = computeFieldValue ();
}
статический тип поля getField () {вернуть FieldHolder.field; }

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

См. Также

4
ответ дан 18 December 2019 в 07:08
поделиться

О ленивой и нетерпеливой инициализации

Оператор if является реализацией техники ленивой инициализации.

Более явная версия выглядит так:

private boolean firstTime = true;
private Stuff stuff;

public Stuff gimmeStuff() {
   if (firstTime) {
      firstTime = false;
      stuff = new Stuff();
   }
   return stuff;
}

Происходит следующее: при первом вызове gimmeStuff(), firstTime будет true, поэтому stuff будет инициализирован в new Stuff(). При последующих вызовах firstTime будет false, поэтому new Stuff() больше не будет вызываться.

Таким образом, stuff инициализируется "лениво". На самом деле он не инициализируется до первого раза, когда он понадобится.

См. также


О безопасности потоков

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

Одно из решений - сделать синхронизированный метод getSingletonObject(). Однако это приводит к накладным расходам на синхронизацию при ALL вызовах getSingletonObject(). Так называемая блокировка с двойной проверкой используется, чтобы попытаться исправить это, но в Java эта идиома фактически не работает до J2SE 5.0 с введением ключевого слова volatile в новой модели памяти.

Излишне говорить, что правильное применение паттерна singleton не является тривиальной задачей.

See also

Похожие вопросы


Effective Java 2nd Edition

Вот что говорит книга по этим вопросам:

Пункт 71: Используйте ленивую инициализацию разумно

Как и в случае с большинством оптимизаций, лучший совет по ленивой инициализации - "не делайте ее, если она вам не нужна". Ленивая инициализация - это обоюдоострый меч. Она снижает стоимость инициализации класса или создания экземпляра за счет увеличения стоимости доступа к лениво инициализированному полю. В зависимости от того, какая доля лениво инициализированных полей в конечном итоге требует инициализации, насколько дорого их инициализировать и как часто происходит обращение к каждому полю, ленивая инициализация (как и многие другие "оптимизации", на самом деле вредит производительности).

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

В большинстве случаев нормальная инициализация предпочтительнее ленивой инициализации.

Пункт 3: Обеспечить свойство singleton с помощью приватного конструктора или enum типа

Начиная с версии 1.5. существует третий подход к реализации singletons. Просто создайте тип enum с одним элементом. [...] Этот подход функционально эквивалентен подходу public field, за исключением того, что он более лаконичен, предоставляет механизм сериализации бесплатно и обеспечивает железную гарантию от множественного инстанцирования даже перед лицом сложных атак на основе сериализации или отражения.

[...] Одноэлементный перечислительный тип - лучший способ реализовать синглтон.

Похожие вопросы

О реализации enum singleton/Java:

О достоинствах паттерна синглтон и альтернативах:

9
ответ дан 18 December 2019 в 07:08
поделиться

Этот код

  • отвечает за создание первого объекта
  • предотвращает создание другого
0
ответ дан 18 December 2019 в 07:08
поделиться

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

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

1
ответ дан 18 December 2019 в 07:08
поделиться

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

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

public static synchronized SingletonObjectDemo getSingletonObject() {
    if (singletonObject == null) {
        singletonObject = new SingletonObjectDemo();
    }
    return singletonObject;
}

Кстати, возвращаясь к вашему вопросу, строка:

private static SingletonObjectDemo singletonObject;

объявляет статическую ссылку, но она не будет фактически выделять экземпляр, ссылка установлена ​​на null компилятором Java.

3
ответ дан 18 December 2019 в 07:08
поделиться

Сначала для singletonObject устанавливается значение null. Идея синглтона состоит в том, чтобы инициализировать этот объект в первый раз, когда кто-то вызывает getSingletonObject (). Если вы не вызываете конструктор в этой части, переменная всегда будет иметь значение NULL.

0
ответ дан 18 December 2019 в 07:08
поделиться
Другие вопросы по тегам:

Похожие вопросы: