Каков эффективный способ реализации одноэлементного шаблона в Java?

785

Что такое эффективный способ реализации одноэлементного шаблона в Java?

  • 0
    «Каков эффективный способ реализации одноэлементного шаблона в Java?» пожалуйста, определите эффективные.
  • 0
    medium.com/@kevalpatel2106/… . Это полная статья о том, как добиться безопасности потоков, отражений и сериализации в шаблоне синглтона. Это хороший источник, чтобы понять преимущества и недостатки синглтон-класса.
Показать ещё 1 комментарий
Теги:
design-patterns
singleton

31 ответ

701
Лучший ответ

Используйте перечисление:

public enum Foo {
    INSTANCE;
}

Джошуа Блох объяснил этот подход в своей Эффективной перезагрузке Java в Google I/O 2008: ссылка на видео. Также см. Слайды 30-32 его презентации (effective_java_reloaded.pdf):

Правильный способ реализации Serializable Singleton

public enum Elvis {
    INSTANCE;
    private final String[] favoriteSongs =
        { "Hound Dog", "Heartbreak Hotel" };
    public void printFavorites() {
        System.out.println(Arrays.toString(favoriteSongs));
    }
}

Изменить: онлайн-часть "Эффективная Java" сообщает:

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

  • 187
    Я думаю, что люди должны начать рассматривать перечисления как класс с особенностями. если вы можете перечислить экземпляры вашего класса во время компиляции, используйте enum.
  • 35
    Я с Амиром. Название «enum» имеет так много предвзятых ожиданий, что требуется некоторое время, чтобы понять, что в Java перечисления действительно намного больше, чем список символических констант.
Показать ещё 23 комментария
223

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

Так как java5 лучший способ сделать это - использовать перечисление:

public enum Foo {
   INSTANCE;
}

Pre java5, самый простой случай:

public final class Foo {

    private static final Foo INSTANCE = new Foo();

    private Foo() {
        if (INSTANCE != null) {
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return INSTANCE;
    }

    public Object clone() throws CloneNotSupportedException{
        throw new CloneNotSupportedException("Cannot clone instance of this class");
    }
}

Перейдите по коду. Во-первых, вы хотите, чтобы класс был окончательным. В этом случае я использовал ключевое слово final, чтобы пользователи знали, что это окончательно. Затем вам нужно сделать конструктор закрытым, чтобы пользователи не могли создавать свой собственный 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("Already instantiated");
        }
    }

    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("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return FooLoader.INSTANCE;
    }

    @SuppressWarnings("unused")
    private Foo readResolve() {
        return FooLoader.INSTANCE;
    }
}

Метод readResolve() гарантирует, что будет возвращен единственный экземпляр, даже если объект был сериализован в предыдущем запуске вашей программы.

  • 31
    Проверка на отражение бесполезна. Если другой код использует рефлексию для рядовых, это Game Over. Нет причин даже пытаться правильно функционировать при таком неправильном использовании. И если вы попробуете, это все равно будет неполная «защита», просто много потраченного впустую кода.
  • 4
    > «Во-первых, вы хотите, чтобы класс был окончательным». Может ли кто-нибудь уточнить это, пожалуйста?
Показать ещё 13 комментариев
121

Отказ от ответственности: Я только что обобщил все замечательные ответы и написал это в своих словах.


При внедрении Singleton у нас есть 2 варианта
1. Ленивая загрузка
2. Ранняя загрузка

Lazy loading добавляет бит накладные расходы (много, если честно), поэтому используйте его только тогда, когда у вас очень большой объект или тяжелый строительный код. И также есть другие доступные статические методы или поля, которые могут быть использованы до того, как экземпляр понадобится, затем и только тогда вам нужно использовать ленивую инициализацию. В противном случае выбор ранней загрузки - хороший выбор.

Самый простой способ реализации Singleton -

public class Foo {

    // It will be our sole hero
    private static final Foo INSTANCE = new Foo();

    private Foo() {
        if (INSTANCE != null) {
            // SHOUT
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return INSTANCE;
    }
}

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

class Foo {

    // Our now_null_but_going_to_be sole hero 
    private static Foo INSTANCE = null;

    private Foo() {
        if (INSTANCE != null) {
            // SHOUT  
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        // Creating only  when required.
        if (INSTANCE == null) {
            INSTANCE = new Foo();
        }
        return INSTANCE;
    }
}

До сих пор так хорошо, но наш герой не выживет, сражаясь один с несколькими злыми нитями, которые хотят многого множества нашего героя. Так что давайте защитим его от злой многопоточности

class Foo {

    private static Foo INSTANCE = null;

    // TODO Add private shouting constructor

    public static Foo getInstance() {
        // No more tension of threads
        synchronized (Foo.class) {
            if (INSTANCE == null) {
                INSTANCE = new Foo();
            }
        }
        return INSTANCE;
    }
}

но этого недостаточно, чтобы защитить героя, на самом деле!!! Это лучшее, что мы можем/должны сделать, чтобы помочь нашему герою

class Foo {

    // Pay attention to volatile
    private static volatile Foo INSTANCE = null;

    // TODO Add private shouting constructor

    public static Foo getInstance() {
        if (INSTANCE == null) { // Check 1
            synchronized (Foo.class) {
                if (INSTANCE == null) { // Check 2
                    INSTANCE = new Foo();
                }
            }
        }
        return INSTANCE;
    }
}

Это называется "Идиома с двойной проверкой блокировки". Легко забыть об изменчивом заявлении и трудно понять, почему это необходимо.
Подробнее: http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

Теперь мы уверены в злой нить, но как насчет жестокой сериализации? Мы должны убедиться, что даже в то время как de-serialiaztion не создается новый объект

class Foo implements Serializable {

    private static final long serialVersionUID = 1L;

    private static volatile Foo INSTANCE = null;

    // Rest of the things are same as above

    // No more fear of serialization
    @SuppressWarnings("unused")
    private Object readResolve() {
        return INSTANCE;
    }
}

Метод readResolve() гарантирует, что будет возвращен единственный экземпляр, даже если объект был сериализован в предыдущем запуске нашей программы.

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

public final class Foo implements Serializable {

    private static final long serialVersionUID = 1L;

    // Wrapped in a inner static class so that loaded only when required
    private static class FooLoader {

        // And no more fear of threads
        private static final Foo INSTANCE = new Foo();
    }

    // TODO add private shouting construcor

    public static Foo getInstance() {
        return FooLoader.INSTANCE;
    }

    // Damn you serialization
    @SuppressWarnings("unused")
    private Foo readResolve() {
        return FooLoader.INSTANCE;
    }
}

Да, это наш самый герой:)
Так как строка private static final Foo INSTANCE = new Foo(); выполняется только тогда, когда фактически используется класс FooLoader, это заботится о ленивом экземпляре,

и гарантируется, что он будет безопасен по потоку.

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

 public enum Foo {
       INSTANCE;
   }

Что внутренне будет рассматриваться как

public class Foo {

    // It will be our sole hero
    private static final Foo INSTANCE = new Foo();
}

Это не страх перед сериализацией, нитями и уродливым кодом. Кроме того, ENUMS singleton лениво инициализируются.

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

-Joshua Bloch в "Эффективной Java"

Теперь вы, возможно, поняли, почему ENUMS считаются лучшим способом реализации Singleton и спасибо за ваше терпение:)
Обновлено на моем блоге.

  • 3
    Просто пояснение: синглтоны, реализованные с помощью enum, инициализируются лениво. Подробности здесь: stackoverflow.com/questions/16771373/…
  • 2
    отличный ответ. И последнее, переопределите метод клонирования, чтобы вызвать исключение.
Показать ещё 6 комментариев
121

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

Легко забыть об изменчивом заявлении и трудно понять, почему это необходимо. Без изменчивости этот код больше не будет потокобезопасным из-за двойного контроля блокировки. Подробнее об этом см. В пункте 16.2.4 Java Concurrency на практике. Вкратце: этот шаблон (до версии Java5.0 или без оператора volatile) может вернуть ссылку на объект Bar, который (еще) находится в неправильном состоянии.

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

class Bar {
    private static class BarHolder {
        public static Bar bar = new Bar();
    }

    public static Bar getBar() {
        return BarHolder.bar;
    }
}
  • 1
    Справедливо! Мне просто комфортно с летучими, и это использовать. Да, и три приветствия для JCiP.
  • 1
    О, это, очевидно, тот подход, который пропагандирует Уильям Пью из FindBugz.
Показать ещё 4 комментария
98

Защита потолка в Java 5 +:

class Foo {
    private static volatile Bar bar = null;
    public static Bar getBar() {
        if (bar == null) {
            synchronized(Foo.class) {
                if (bar == null)
                    bar = new Bar(); 
            }
        }
        return bar;
    }
}

EDIT. Обратите внимание на модификатор volatile.:) Это важно, потому что без него, другие потоки не гарантируются JMM (модель памяти Java), чтобы увидеть изменения его значения. Синхронизация не позаботится об этом - она ​​только сериализует доступ к этому блоку кода.

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

  • 1
    Где я могу узнать больше о модификаторе volatile?
  • 0
    Смотрите комментарии на stackoverflow.com/questions/70689/…
Показать ещё 7 комментариев
86

Забудьте ленивую инициализацию, это слишком проблематично. Это самое простое решение:

public class A {    

    private static final A INSTANCE = new A();

    private A() {}

    public static A getInstance() {
        return INSTANCE;
    }
}
  • 21
    переменная экземпляра синглтона также может быть финальной. например, частный статический финал A singleton = new A ();
  • 17
    По сути, это ленивая инициализация, так как статический синглтон не будет создаваться до тех пор, пока класс не будет загружен, а класс не будет загружен до тех пор, пока он не понадобится (что будет верно во время первого обращения к методу getInstance ()).
Показать ещё 9 комментариев
47

Убедитесь, что он вам действительно нужен. Сделайте google для "singleton anti-pattern", чтобы увидеть некоторые аргументы против него. Похоже, что в этом нет ничего неправильного, но это всего лишь механизм для обнародования некоторых глобальных ресурсов/данных, поэтому убедитесь, что это лучший способ. В частности, я нашел инъекцию зависимостей более полезной, особенно если вы также используете модульные тесты, потому что DI позволяет использовать издеваемые ресурсы для целей тестирования.

  • 0
    Вы можете также вводить фиктивные значения с помощью традиционного метода, но я думаю, что это не стандартный / srping способ, поэтому его дополнительная работа с использованием только унаследованного кода ...
27

Не забывайте, что Singleton - это только Singleton для загрузчика классов, который загружал его. Если вы используете несколько загрузчиков (контейнеров), у каждого из них должна быть своя версия Singleton.

  • 2
    Чтобы избежать использования служебного класса, зарегистрируйте контейнер, содержащий экземпляр singleton, на платформе MBeanServer. ObjectName - это ~ одноэлементное имя класса. Если значение INSTANCE равно нулю, getInstance проверяет MBeanServer. Если MBean существует, доступ к экземпляру осуществляется через атрибут в контейнере.
22

Я озадачен некоторыми ответами, которые предлагают DI как альтернативу использованию синглетонов; это не связанные понятия. Вы можете использовать DI для ввода одномодовых или не одиночных (например, для потоков) экземпляров. По крайней мере, это верно, если вы используете Spring 2.x, я не могу говорить для других инфраструктур DI.

Итак, мой ответ на OP был бы (во всех, кроме самого тривиального кода примера):

  • Используйте инфраструктуру DI, например Spring, затем
  • Сделайте это частью вашей конфигурации DI, независимо от того, являются ли ваши зависимости одноточечными, запрашивать область действия, область сеанса или что-то еще.

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

  • 4
    Возможно, потому что люди не согласны с вами. Я не проголосовал против вас, но я не согласен: я думаю, что DI может быть использован для решения тех же проблем, которые есть у одиночек. Это основано на понимании «синглтона», означающего «объект с единственным экземпляром, к которому напрямую осуществляется глобальное имя», а не просто «объект с единственным экземпляром», что, возможно, немного сложно.
  • 2
    Чтобы немного TicketNumberer это, рассмотрим TicketNumberer который должен иметь один глобальный экземпляр и в котором вы хотите написать класс TicketIssuer который содержит строку кода int ticketNumber = ticketNumberer.nextTicketNumber(); , В традиционном одноэлементном мышлении предыдущая строка кода должна выглядеть примерно так: TicketNumberer ticketNumberer = TicketNumberer.INSTANCE; , С public TicketIssuer(TicketNumberer ticketNumberer) { this.ticketNumberer = ticketNumberer; } DI, класс будет иметь конструктор, подобный public TicketIssuer(TicketNumberer ticketNumberer) { this.ticketNumberer = ticketNumberer; }
Показать ещё 2 комментария
19

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

Лично я стараюсь избегать синглетонов как можно чаще по многим причинам, и большинство из них можно найти в сингл-сайтах с поисковой системой. Я чувствую, что довольно часто одиночные игры злоупотребляют, потому что их легко понять всеми, они используются как механизм для получения "глобальных" данных в OO-дизайне, и они используются потому, что легко обойти управление жизненным циклом объекта (или действительно думая о том, как вы можете делать A изнутри B). Посмотрите на такие вещи, как Inversion of Control (IoC) или Injection Dependency (DI) для хорошего среднего уровня.

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

  • 0
    Согласовано. Это скорее основополагающий класс, который запускает остальную часть вашего приложения, и в случае его дублирования вы получите полный хаос (т. Е. Одиночный доступ к ресурсу или обеспечение безопасности). Передача глобальных данных по всему вашему приложению - это большой красный флаг связи. Используйте это, когда вы признаете, что вам действительно это нужно.
16

Ниже приведены 3 разных подхода

1) Enum

/**
* Singleton pattern example using Java Enumj
*/
public enum EasySingleton{
    INSTANCE;
}

2) Двойная проверка Блокировка/ленивая загрузка

/**
* Singleton pattern example with Double checked Locking
*/
public class DoubleCheckedLockingSingleton{
     private static volatile DoubleCheckedLockingSingleton INSTANCE;

     private DoubleCheckedLockingSingleton(){}

     public static DoubleCheckedLockingSingleton getInstance(){
         if(INSTANCE == null){
            synchronized(DoubleCheckedLockingSingleton.class){
                //double checking Singleton instance
                if(INSTANCE == null){
                    INSTANCE = new DoubleCheckedLockingSingleton();
                }
            }
         }
         return INSTANCE;
     }
}

3) Статический метод factory

/**
* Singleton pattern example with static factory method
*/

public class Singleton{
    //initailzed during class loading
    private static final Singleton INSTANCE = new Singleton();

    //to prevent creating another instance of Singleton
    private Singleton(){}

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

Я использую Spring Framework для управления моими синглонами. Он не применяет "singleton-ness" класса (который вы не можете делать в любом случае, если задействованы несколько загрузчиков классов), но обеспечивает очень простой способ создания и настройки различных фабрик для создания различных типов объектов.

11

Версия 1:

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

Lazy loading, потокобезопасность с блокировкой, низкая производительность из-за synchronized.

Версия 2:

public class MySingleton {
    private MySingleton() {}
    private static class MySingletonHolder {
        public final static MySingleton instance = new MySingleton();
    }
    public static MySingleton getInstance() {
        return MySingletonHolder.instance;
    }
}

Lazy loading, потокобезопасная с неблокирующейся, высокая производительность.

10

Если вам не нужна ленивая загрузка, просто попробуйте

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();
    }
}

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

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 или создать реестр для всех ваши синглтоны.

  • 2
    Первая версия лучшая. Предполагая, что класс ничего не делает, кроме предоставления синглтона, он обычно создается примерно в той же точке, что и во второй версии, из-за отложенной загрузки класса.
  • 1
    Двойная проверка бессмысленна для статики. И почему вы сделали публично защищенный метод клонирования?
Показать ещё 4 комментария
10

В Википедии есть несколько примеров одиночных игр, также на Java. Реализация Java 5 выглядит довольно полной и является потокобезопасной (применяется двойная проверка блокировки).

8

Я бы сказал Enum singleton

Синглтон, использующий enum в Java, обычно является способом объявления enum singleton. Enum singleton может содержать экземплярную переменную и метод экземпляра. Для простоты также обратите внимание, что если вы используете какой-либо метод экземпляра, чем вам необходимо обеспечить безопасность потока этого метода, если он вообще влияет на состояние объекта.

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

/**
* Singleton pattern example using Java Enum
*/
public enum Singleton {
        INSTANCE;
        public void execute (String arg) {
                //perform operation here
        }
}

Вы можете получить к нему доступ Singleton.INSTANCE, гораздо проще, чем вызов метода getInstance() на Singleton.

1.12 Сериализация констант перечисления

Константы континуума сериализуются иначе, чем обычные сериализуемые или внешние объекты. Сериализованная форма константы перечисления состоит исключительно из ее имени; значения поля константы отсутствуют в форме. Чтобы сериализовать константу перечисления, ObjectOutputStream записывает значение, возвращаемое методом имен констант enum. Чтобы десериализовать константу перечисления, ObjectInputStream считывает имя константы из потока; десериализованная константа затем получается путем вызова метода java.lang.Enum.valueOf, передающего константный тип перечисления вместе с полученным именем константы в качестве аргументов. Как и другие сериализуемые или внешние объекты, константы перечисления могут функционировать в качестве целей обратных ссылок, появляющихся впоследствии в потоке сериализации.

Процесс, с помощью которого константы контировки сериализуются, не может быть настроен: любые методы класса writeObject, readObject, readObjectNoData, writeReplace и readResolve, определенные типами перечислений, игнорируются при сериализации и десериализации, Аналогично, любые объявления полей serialPersistentFields или serialVersionUID также игнорируются - все типы перечислений имеют фиксированный serialVersionUID of 0L. Документирование сериализуемых полей и данных для типов перечислений не требуется, поскольку нет изменений в типе отправленных данных.

Цитируется из документации Oracle

Другая проблема с обычными синглтонами заключается в том, что после реализации интерфейса Serializable они больше не остаются Singleton, потому что метод readObject() всегда возвращает новый экземпляр типа конструктора в Java. Этого можно избежать, используя readResolve() и отбрасывая вновь созданный экземпляр, заменив его синглоном, как показано ниже

 // readResolve to prevent another instance of Singleton
 private Object readResolve(){
     return INSTANCE;
 }

Это может стать еще более сложным, если ваш Singleton Class поддерживает состояние, так как вам нужно сделать его переходным, но с Enum Singleton, Serialization гарантируется JVM.


Хорошее чтение

6
There are 4 ways to create a singleton in java.

1- eager initialization singleton

    public class Test{
        private static final Test test = new Test();
        private Test(){}
        public static Test getTest(){
            return test;
        }
    }

2- lazy initialization singleton (thread safe)

    public class Test {
         private static volatile Test test;
         private Test(){}
         public static Test getTest() {
            if(test == null) {
                synchronized(Test.class) {
                    if(test == null){test = new Test();
                }
            }
         }

        return test;
    }


3- Bill Pugh Singleton with Holder Pattern (Preferably the best one)

    public class Test {

        private Test(){}

        private static class TestHolder{
            private static final Test test = new Test();
        }

        public static Test getInstance(){
            return TestHolder.test;
        }
    }

4- enum singleton
      public enum MySingleton {
        INSTANCE;
    private MySingleton() {
        System.out.println("Here");
    }
}
  • 0
    (1) не стремится, это лениво из-за механизма загрузки классов JVM.
  • 0
    @ Miha_x64, когда я сказал «нетерпеливая загрузка», я сказал «нетерпеливая инициализация». Если вы думаете, что они одинаковы, то что и загружается. Возможно, вам следует написать книгу и исправить ошибки, допущенные предыдущими авторами, такими как Джошуа Блох.
Показать ещё 3 комментария
5

Может быть, немного опоздали с игрой по этому вопросу, но в реализации синглтона есть много нюансов. Держатель рисунка нельзя использовать во многих ситуациях. И IMO при использовании volatile - вы также должны использовать локальную переменную. Позвольте начать с начала и повторить проблему. Вы поймете, что я имею в виду.


Первая попытка может выглядеть примерно так:

public class MySingleton {

     private static MySingleton INSTANCE;

     public static MySingleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new MySingleton();
        }

        return INSTANCE;
    }
    ...
}

Здесь у нас есть класс MySingleton, который имеет закрытый статический член с именем INSTANCE и открытый статический метод с именем getInstance(). При первом вызове getInstance() член INSTANCE имеет значение null. Затем поток перейдет в состояние создания и создаст новый экземпляр класса MySingleton. Последующие вызовы getInstance() обнаружат, что переменная INSTANCE уже установлена, и, следовательно, не создадут другой экземпляр MySingleton. Это гарантирует, что есть только один экземпляр MySingleton, который используется всеми вызывающими getInstance().

Но у этой реализации есть проблема. У многопоточных приложений будет условие гонки при создании единственного экземпляра. Если несколько потоков выполнения попадают в метод getInstance() одновременно (или около того), каждый из них будет видеть элемент INSTANCE как нулевой. Это приведет к тому, что каждый поток создаст новый экземпляр MySingleton и впоследствии установит элемент INSTANCE.


private static MySingleton INSTANCE;

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

    return INSTANCE;
}

Здесь мы использовали ключевое слово synchronized в сигнатуре метода для синхронизации метода getInstance(). Это, безусловно, исправит наше состояние гонки. Теперь потоки будут блокироваться и вводить метод по одному. Но это также создает проблему производительности. Эта реализация не только синхронизирует создание отдельного экземпляра, но и все вызовы getInstance(), включая чтение. Чтения не должны быть синхронизированы, поскольку они просто возвращают значение INSTANCE. Поскольку чтение будет составлять основную часть наших вызовов (помните, что создание экземпляров происходит только при первом вызове), мы подвергнемся ненужному снижению производительности при синхронизации всего метода.


private static MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronize(MySingleton.class) {
            INSTANCE = new MySingleton();
        }
    }

    return INSTANCE;
}

Здесь мы перенесли синхронизацию из сигнатуры метода в синхронизированный блок, который оборачивает создание экземпляра MySingleton. Но решает ли это нашу проблему? Что ж, мы больше не блокируем чтения, но мы также сделали шаг назад. Несколько потоков попадут в метод getInstance() в одно и то же время или около того, и все они увидят элемент INSTANCE как нулевой. Затем они попадут в синхронизированный блок, где можно получить блокировку и создать экземпляр. Когда этот поток выходит из блока, другие потоки будут бороться за блокировку, и один за другим каждый поток будет проходить через блок и создавать новый экземпляр нашего класса. Итак, мы вернулись туда, откуда начали.


private static MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronized(MySingleton.class) {
            if (INSTANCE == null) {
                INSTANCE = createInstance();
            }
        }
    }

    return INSTANCE;
}

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

Это решает нашу проблему множественной реализации. Но еще раз, наше решение поставило еще одну проблему. Другие темы могут не "видеть", что элемент INSTANCE обновлен. Это из-за того, как Java оптимизирует операции с памятью. Потоки копируют исходные значения переменных из основной памяти в кэш ЦП. Изменения значений затем записываются в этот кэш и считываются из него. Это особенность Java, предназначенная для оптимизации производительности. Но это создает проблему для нашей одноэлементной реализации. Второй поток, обрабатываемый другим процессором или ядром с использованием другого кэша, не увидит изменений, внесенных первым. Это приведет к тому, что второй поток увидит член INSTANCE как ноль, что приведет к созданию нового экземпляра нашего синглтона.


private static volatile MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronized(MySingleton.class) {
            if (INSTANCE == null) {
                INSTANCE = createInstance();
            }
        }
    }

    return INSTANCE;
}

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

Но это простое изменение имеет свою цену. Поскольку мы обходим кэш-память ЦП, мы будем снижать производительность каждый раз, когда работаем с изменчивым членом INSTANCE, что мы делаем 4 раза. Мы дважды проверяем существование (1 и 2), устанавливаем значение (3), а затем возвращаем значение (4). Можно утверждать, что этот путь является крайним случаем, поскольку мы создаем экземпляр только во время первого вызова метода. Возможно, удар по производительности на создание терпимо. Но даже наш основной вариант использования, читает, будет работать на volatile член дважды. Один раз проверить существование, и снова вернуть его значение.


private static volatile MySingleton INSTANCE;

public static MySingleton getInstance() {
    MySingleton result = INSTANCE;
    if (result == null) {
        synchronized(MySingleton.class) {
            result = INSTANCE;
            if (result == null) {
                INSTANCE = result = createInstance();
            }
        }
    }

    return result;
}

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

Я недавно написал статью об этом. Деконструкция Синглтона. Вы можете найти больше информации об этих примерах и пример паттерна "holder". Существует также реальный пример, демонстрирующий изменчивый подход с двойной проверкой. Надеюсь это поможет.

  • 0
    Не могли бы вы объяснить, почему BearerToken instance в вашей статье не является static ? И что это за результат result.hasExpired() ?
  • 0
    А как насчет class MySingleton - может быть, он должен быть final ?
Показать ещё 1 комментарий
3

Вот как реализовать простой singleton:

public class Singleton {
    // It must be static and final to prevent later modification
    private static final Singleton INSTANCE = new Singleton();
    /** The constructor must be private to prevent external instantiation */ 
    private Singleton(){}
    /** The public static method allowing to get the instance */
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

Вот как правильно лениво создавать свой singleton:

public class Singleton {
    // The constructor must be private to prevent external instantiation   
    private Singleton(){}
    /** The public static method allowing to get the instance */
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
    /** 
     * The static inner class responsible for creating your instance only on demand,
     * because the static fields of a class are only initialized when the class
     * is explicitly called and a class initialization is synchronized such that only 
     * one thread can perform it, this rule is also applicable to inner static class
     * So here INSTANCE will be created only when SingletonHolder.INSTANCE 
     * will be called
     */
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
}
  • 0
    Оба ленивы, предполагая, что единственное, что вам нужно от синглтона - это его экземпляр.
  • 0
    @ Miha_x64 первый случай будет создавать экземпляр синглтона, когда JVM инициализирует класс, второй случай будет создавать экземпляр только синглтона при вызове getInstance() . Но на самом деле, если у вас нет других статических методов в вашем классе Singleton и вы вызываете только getInstance() реальной разницы нет.
3

Enum singleton

Простейшим способом реализации Singleton, который является потокобезопасным, является использование Enum

public enum SingletonEnum {
  INSTANCE;
  public void doSomething(){
    System.out.println("This is a singleton");
  }
}

Этот код работает с момента появления Enum в Java 1.5

Двойная проверка блокировки

Если вы хотите закодировать "классический" синглтон, который работает в многопоточной среде (начиная с Java 1.5), вы должны использовать этот.

public class Singleton {

  private static volatile Singleton instance = null;

  private Singleton() {
  }

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

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

Ранняя загрузка Singleton (работает даже до Java 1.5)

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

public class Singleton {

  private static final Singleton instance = new Singleton();

  private Singleton() {
  }

  public static Singleton getInstance() {
    return instance;
  }

  public void doSomething(){
    System.out.println("This is a singleton");
  }

}
3

Различные способы создания одиночного объекта:

  • В соответствии с Джошуа Блох - Эннум будет лучшим.

  • вы также можете использовать двойную блокировку.

  • Можно использовать даже внутренний статический класс.

3

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

Кроме того, если singleton должен быть seriliazble, все остальные поля должны быть временными, а метод readResolve() должен быть реализован для поддержания инварианта одиночного объекта. В противном случае, каждый раз, когда объект десериализуется, будет создан новый экземпляр объекта. То, что readResolve() делает, заменяет новый объект, считанный readObject(), который заставлял этот новый объект быть собранным мусором, поскольку нет переменной, ссылающейся на него.

public static final INSTANCE == ....
private Object readResolve() {
  return INSTANCE; // original singleton instance.
} 
2

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

public class SingletonImpl {

    private static SingletonImpl instance;

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

    public static void setInstance(SingletonImpl impl) {
        instance = impl;
    }

    public void a() {
        System.out.println("Default Method");
    }
}

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

public class SingletonMock extends SingletonImpl {

    @Override
    public void a() {
        System.out.println("Mock Method");
    }

}

Это также работает с ранними подходами инициализации:

public class SingletonImpl {

    private static final SingletonImpl instance = new SingletonImpl();

    private static SingletonImpl alt;

    public static void setInstance(SingletonImpl inst) {
        alt = inst;
    }

    public static SingletonImpl getInstance() {
        if (alt != null) {
            return alt;
        }
        return instance;
    }

    public void a() {
        System.out.println("Default Method");
    }
}

public class SingletonMock extends SingletonImpl {

    @Override
    public void a() {
        System.out.println("Mock Method");
    }

}

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

Тем не менее, для возможности макета-тестирования (при необходимости), это воздействие кода может быть приемлемой ценой.

2

Для JSE 5.0 и выше применяйте подход Enum, в противном случае используйте статический подход к одноэлементному держателю ((ленивый подход к загрузке, описанный Биллом Пью). Решение Latter также является потокобезопасным, не требуя специальных языковых конструкций (например, летучих или синхронизированных).

1

простейший одноэлементный класс

public class Singleton {
  private static Singleton singleInstance = new Singleton();
  private Singleton() {}
  public static Singleton getSingleInstance() {
    return singleInstance;
  }
}
  • 1
    это так же, как ответ Джонатана ниже
  • 1
    Дубликат этого братского ответа Джонатана, размещенный пятью годами ранее. Смотрите этот ответ для интересных комментариев.
0

Лучший сингл-модель, которую я когда-либо видел, использует интерфейс поставщика.

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

Увидеть ниже:

public class Singleton<T> implements Supplier<T> {

    private boolean initialized;
    private Supplier<T> singletonSupplier;

    public Singleton(T singletonValue) {
        this.singletonSupplier = () -> singletonValue;
    }

    public Singleton(Supplier<T> supplier) {
        this.singletonSupplier = () -> {
            // The initial supplier is temporary; it will be replaced after initialization
            synchronized (supplier) {
                if (!initialized) {
                    T singletonValue = supplier.get();
                    // Now that the singleton value has been initialized,
                    // replace the blocking supplier with a non-blocking supplier
                    singletonSupplier = () -> singletonValue;
                    initialized = true;
                }
                return singletonSupplier.get();
            }
        };
    }

    @Override
    public T get() {
        return singletonSupplier.get();
    }
}
0

Чтобы достичь этого (TRUE Singleton),

  • Убедитесь, что ваш класс final. Другие не могут подклассифицировать его и создать еще один экземпляр
  • Сделайте свой объект singleton как private static final
  • Предоставить метод private constructor и public getInstance().
  • Убедитесь, что этот класс Singleton загружен только одним ClassLoader
  • переопределить readResolve() и вернуть тот же экземпляр.

Полезные ссылки: Все ответы в этом сообщении +

Singleton_pattern: Инициализация по требованию с идиомой из wikipedia

journaldev статья

Демонстрация различных способов достижения Singleton

public final class  Singleton{
    private static final Singleton instance = new Singleton();

    public static Singleton getInstance(){
        return instance; 
    }
    public enum EnumSingleton {
        INSTANCE;   
    }
    private Object readResolve()  {
        return instance;
    }
    public static void main(String args[]){
        System.out.println("Singleton:"+Singleton.getInstance());
        System.out.println("Enum.."+EnumSingleton.INSTANCE);
        System.out.println("Lazy.."+LazySingleton.getInstance());
    }
}
final class  LazySingleton {
    private LazySingleton() {}
    public static LazySingleton getInstance() {
        return LazyHolder.INSTANCE;
    }
    private static class LazyHolder {
        private static final LazySingleton INSTANCE = new LazySingleton();
    }
    private Object readResolve()  {
        return LazyHolder.INSTANCE;
    }
}

выход:

Singleton:Singleton@5eb1404f
Enum..INSTANCE
Lazy..LazySingleton@46fb3d6
0

Взгляните на это сообщение.

Примеры шаблонов проектирования GoF в основных библиотеках Java

Из лучшего ответа "Singleton",

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

  • java.lang.Runtime # getRuntime()
  • java.awt.Desktop # getDesktop()
  • java.lang.System # getSecurityManager()

Вы также можете узнать пример Singleton из самих классов Java.

0

Я все еще думаю после java 1.5, enum - лучшая доступная реализация singleton, так как она также гарантирует, что даже в многопоточных средах - создается только один экземпляр.

public enum Singleton{ INSTANCE; }

и вы закончили!!!

  • 1
    Это уже упоминалось в других ответах лет назад.
-2

Иногда простого " static Foo foo = new Foo();" недостаточно. Просто подумайте о какой-то базовой вставке данных, которую вы хотите сделать.

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

public class Singleton {

    private static Singleton instance = null;

    static {
          instance = new Singleton();
          // do some of your instantiation stuff here
    }

    private Singleton() {
          if(instance!=null) {
                  throw new ErrorYouWant("Singleton double-instantiation, should never happen!");
          }
    }

    public static getSingleton() {
          return instance;
    }

}

Теперь, что происходит? Класс загружается через загрузчик классов. Непосредственно после интерпретации класса из массива байтов VM выполняет блок static {}. что весь секрет: статический блок вызывается только один раз, время, которое данный класс (имя) данного пакета загружается этим одноклассным загрузчиком.

  • 3
    Не правда. статические переменные инициализируются вместе со статическими блоками при загрузке класса. Не нужно разделять декларацию.
-5
public class Singleton {

    private static final Singleton INSTANCE = new Singleton();

    private Singleton(){
    if (INSTANCE != null)
        throw new IllegalStateException ("Already instantiated...");
}

    public synchronized static Singleton getInstance() { 
    return INSTANCE;

    }

}

Как мы добавили ключевое слово Synchronized перед getInstance, мы избегали условия гонки в случае, когда два потока одновременно называют getInstance.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню