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

812

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

Рияз Мохаммед Ибрагим
источник
1
«Каков эффективный способ реализации одноэлементного шаблона в Java?» пожалуйста, определите эффективные.
Мариан Падзиох,
medium.com/@kevalpatel2106/… . Это полная статья о том, как добиться безопасности потоков, отражений и сериализации в шаблоне синглтона. Это хороший источник, чтобы понять преимущества и недостатки синглтон-класса.
Кевал Патель
Как указывает Джошуа Блох в «Эффективной Java», enum singleton - лучший путь. Здесь я классифицировал различные реализации как ленивые / энергичные и т. Д.
isaolmez

Ответы:

782

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

public enum Foo {
    INSTANCE;
}

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

Правильный способ реализации сериализуемого синглтона

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

Edit: онлайн часть «Эффективное Java» , говорит:

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

Стивен Денн
источник
203
Я думаю, что люди должны начать рассматривать перечисления как класс с особенностями. если вы можете перечислить экземпляры вашего класса во время компиляции, используйте enum.
Амир Арад
7
Лично я не часто нахожу необходимость использовать шаблон синглтона напрямую. Иногда я использую инъекцию зависимостей Spring с контекстом приложения, который содержит то, что он называет синглетами. Мои служебные классы, как правило, содержат только статические методы, и мне не нужны их экземпляры.
Стивен Денн
3
Привет, кто-нибудь может сказать мне, как этот тип синглтона может быть смоделирован и проверен в тестовых случаях. Я попытался заменить поддельный экземпляр синглтона для этого типа, но не смог.
Ашиш Шарма
29
Я думаю, это имеет смысл, но мне все еще не нравится это. Как бы вы создали синглтон, который расширяет другой класс? Если вы используете перечисление, вы не можете.
Чарви
11
@bvdb: Если вам нужна большая гибкость, вы уже облажались, внедрив вначале синглтон. Возможность создать независимый экземпляр, когда он вам нужен, сама по себе бесценна.
cHao
233

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

Начиная с java5, лучший способ сделать это - использовать enum:

public enum Foo {
   INSTANCE;
}

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

Роэл Спилкер
источник
32
Проверка на отражение бесполезна. Если другой код использует размышления о рядовых, это игра окончена. Нет причин даже пытаться правильно функционировать при таком неправильном использовании. И если вы попробуете, это все равно будет неполная «защита», просто много потраченного впустую кода.
Wouter Coekaerts
5
> «Во-первых, вы хотите, чтобы класс был последним». Может ли кто-нибудь уточнить это, пожалуйста?
PlagueHammer
2
Защита десериализации полностью нарушена (я думаю, что это упоминается в Effective Java 2nd Ed).
Том Хотин - Tackline
9
-1 это абсолютно не самый простой случай, он придуман и излишне сложен. Посмотрите на ответ Джонатана, чтобы найти самое простое решение, которого достаточно в 99,9% случаев.
Майкл Боргвардт
2
Это полезно, когда ваш синглтон должен наследоваться от суперкласса. В этом случае нельзя использовать одноэлементный шаблон enum, поскольку перечисления не могут иметь суперкласс (хотя они могут реализовывать интерфейсы). Например, Google Guava использует статическое конечное поле, когда синглтон-шаблон enum недоступен: code.google.com/p/guava-libraries/source/browse/trunk/guava/src/…
Этьен Неве
139

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


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

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

Самый простой способ реализации 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

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

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 синглтон лениво инициализируется .

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

Джошуа Блох в "Эффективной Яве"

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

хуг
источник
3
Просто пояснение: синглтоны, реализованные с помощью enum, инициализируются лениво. Подробности здесь: stackoverflow.com/questions/16771373/…
2
отличный ответ. И последнее, переопределите метод клонирования, чтобы вызвать исключение.
riship89
2
@xyz хорошие объяснения, я действительно наслаждался и учился очень легко, и я надеюсь, что никогда не забывал это
Premraj
1
Один из лучших ответов, которые я когда-либо читал на stackoverflow. Спасибо!
Шай Цадок
1
Там является проблема сериализации с помощью перечислений как синглтон: Любые значения поля - члены не сериализовать и , следовательно , не восстанавливаются. См. Спецификацию Сериализации Объекта Java, версия 6.0 . Еще одна проблема: Нет версий - все виды перечислений имеют фиксированный serialVersionUIDиз 0L. Третья проблема: нет настройки. Любые специфичные для класса методы writeObject, readObject, readObjectNoData, writeReplace и readResolve, определенные типами enum, игнорируются во время сериализации и десериализации.
Василий Бурк
124

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

Легко забыть изменчивое утверждение и трудно понять, почему это необходимо. Без volatile этот код больше не был бы безопасен для потоков благодаря двойной проверке антипаттерна блокировки. Подробнее об этом см. В параграфе 16.2.4 Java-параллелизма на практике . Вкратце: этот шаблон (до 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
@Stu Первое издание Effective Java (авторское право 2001 года) детализирует этот шаблон по пункту 48.
Паскаль Тивент
9
@Bno: А как насчет того, чтобы сделать конструктор приватным?
xyz
2
@ AlikElzin-kilaka Не совсем. Экземпляр создается на этапе загрузки класса для BarHolder , который откладывается до тех пор, пока он не понадобится в первый раз. Конструктор Bar может быть настолько сложным, насколько вы хотите, но он не будет вызван до первого getBar(). (И если getBarон называется «слишком рано», то вы столкнетесь с одной и той же проблемой, независимо от того, как реализованы синглоны.) Вы можете увидеть ленивую загрузку классов приведенного выше кода здесь: pastebin.com/iq2eayiR
Ti Strga
95

Потокобезопасен в 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;
    }
}

РЕДАКТИРОВАТЬ : обратите внимание на volatileмодификатор здесь. :) Это важно, потому что без него JMM (модель памяти Java) не гарантирует другие потоки, чтобы увидеть изменения в его значении. Синхронизация не заботится об этом - она ​​только сериализует доступ к этому блоку кода.

РЕДАКТИРОВАТЬ 2 : Ответ @Bno подробно описывает подход, рекомендованный Биллом Пью (FindBugs), и, возможно, лучше. Читайте и голосуйте за его ответ тоже.

Стю Томпсон
источник
1
Где я могу узнать больше о модификаторе volatile?
одиннадцать81
Смотрите комментарии на stackoverflow.com/questions/70689/…
Паскаль Тивент
2
Я думаю, что важно упомянуть о рефлексии атак. Да, большинству разработчиков не нужно беспокоиться, но, похоже, что подобные примеры (по сравнению с синглетонами на основе Enum) должны включать либо код, защищающий от атак с несколькими экземплярами, либо просто добавлять отказ от ответственности, указывающий на такие возможности.
luis.espinal
2
Ключевое слово volatile здесь не требуется - поскольку синхронизация дает взаимное исключение и видимость памяти.
Hemant
2
Зачем все это в Java 5+? Я понимаю, что подход enum обеспечивает безопасность потоков и ленивую инициализацию. Это также намного проще ... Более того, если вы хотите избежать перечисления, я бы все же запретил подход с использованием вложенных статических классов ...
Alexandros
91

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

public class A {    

    private static final A INSTANCE = new A();

    private A() {}

    public static A getInstance() {
        return INSTANCE;
    }
}
Джонатан
источник
21
переменная экземпляра синглтона также может быть финальной. например, частный статический финал A singleton = new A ();
Jatanp
19
По сути, это ленивая инициализация, так как статический синглтон не будет создаваться до тех пор, пока класс не будет загружен, а класс не будет загружен до тех пор, пока он не понадобится (что будет справедливо во время первой ссылки на метод getInstance ()).
Дэн Дайер
7
Если класс A загружается намного раньше, чем вы хотите создать экземпляр static, вы можете обернуть static в статический внутренний класс, чтобы отделить инициализацию класса.
Том Хотин - tackline
3
Я согласен, что этот ответ самый простой, и Анирудхан, нет необходимости объявлять экземпляр окончательным. Никакой другой поток не получит доступ к классу, пока статические члены инициализируются. это гарантируется компилятором, другими словами, вся статическая инициализация выполняется синхронизированным образом - только один поток.
Inor
4
У этого подхода есть одно ограничение: конструктор не может генерировать исключение.
Ванф
47

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

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

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

Таким образом, мой ответ на OP будет (во всем, кроме самого простого примера кода):

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

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

Эндрю Свон
источник
4
Возможно, потому что люди не согласны с вами. Я не проголосовал против вас, но я не согласен: я думаю, что DI может быть использован для решения тех же проблем, которые есть у одиночек. Это основано на понимании «синглтона», означающего «объект с единственным экземпляром, к которому напрямую осуществляется глобальное имя», а не просто «объект с единственным экземпляром», что, возможно, немного сложно.
Том Андерсон
2
Чтобы немного углубиться в это, рассмотрим объект, TicketNumbererкоторый должен иметь один глобальный экземпляр, и где вы хотите написать класс, TicketIssuerсодержащий строку кода int ticketNumber = ticketNumberer.nextTicketNumber();. В традиционном одноэлементном мышлении предыдущая строка кода должна выглядеть примерно так TicketNumberer ticketNumberer = TicketNumberer.INSTANCE;. В DI мышлении, класс будет иметь конструктор, как public TicketIssuer(TicketNumberer ticketNumberer) { this.ticketNumberer = ticketNumberer; }.
Том Андерсон
2
И это становится чужой проблемой, чтобы вызвать этот конструктор. Структура DI сделала бы это с какой-то глобальной картой; DI-архитектура, созданная вручную, будет делать это, потому что mainметод приложения (или один из его миньонов) создаст зависимость и затем вызовет конструктор. По сути, использование глобальной переменной (или глобального метода) является простой формой страшного шаблона локатора службы и может быть заменено внедрением зависимости, как и любое другое использование этого шаблона.
Том Андерсон
@ TomAnderson Я действительно не понимаю, почему люди «боятся» шаблона поиска сервисов. Я думаю, что в большинстве случаев это излишне или не нужно в лучшем случае, однако есть, казалось бы, полезные случаи. С меньшим количеством параметров DI определенно предпочтительнее, но представьте себе 20+. Утверждение, что код не структурирован, не является допустимым аргументом, потому что иногда группировки параметров просто не имеют смысла. Кроме того, с точки зрения модульного тестирования, меня не волнует тестирование сервиса, только его бизнес-логика, и если он правильно закодирован, то это будет легко. Я видел это только в очень крупных проектах.
Брэндон Лин
20

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

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

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


источник
Согласовано. Это скорее основополагающий класс, который запускает остальную часть вашего приложения, и в случае его дублирования вы получите полный хаос (т. Е. Одиночный доступ к ресурсу или обеспечение безопасности). Передача глобальных данных по всему вашему приложению - это большой красный флаг связи. Используйте это, когда вы признаете, что вам действительно это нужно.
Сальвадор Валенсия
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) Статический фабричный метод

/**
* 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 для управления своими синглетонами. Он не обеспечивает «одноэлементность» класса (что на самом деле невозможно, если задействовано несколько загрузчиков классов), но обеспечивает действительно простой способ создания и настройки различных фабрик для создания объектов разных типов.

Matt
источник
11

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

macbirdie
источник
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;
    }
}

Ленивая загрузка, потокобезопасность с блокировкой, низкая производительность из-за 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;
    }
}

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

coderz
источник
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
Двойная проверка бессмысленна для статики. И почему вы сделали публично защищенный метод клонирования?
Том Хотин - tackline
1
-1 Ваша версия двойной проверки блокировки не работает.
assylias
5
Также вам нужно сделать свою переменную синглтонаvolatile
MyTitle
Первая версия является ленивой и потокобезопасно.
Miha_x64
9

Я бы сказал, Enum Singleton

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

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

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

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

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

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

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

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

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

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

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


Хорошо читать

  1. Синглтон
  2. Перечисления, синглтоны и десериализация
  3. Двойная проверка блокировки и шаблон Singleton
NullPoiиteя
источник
8
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");
    }
}
Дирадж Сашан
источник
(1) не стремится, он ленив из-за механизма загрузки классов JVM.
Miha_x64
@ Miha_x64, когда я сказал «нетерпеливая загрузка», я сказал «нетерпеливая инициализация». Если вы думаете, что они одинаковы, то что и загружается. Возможно, вам следует написать книгу и исправить ошибки, допущенные предыдущими авторами, такими как Джошуа Блох.
Дирадж Сашан
Эффективная Java - отличная книга, но, безусловно, требует редактирования.
Miha_x64
@ Miha_x64, что очень хочется загружать, ты можешь объяснить примером
Dheeraj Sachan,
Делать что-то «с нетерпением» означает «как можно скорее». Например, Hibernate поддерживает загрузку отношений с нетерпением, если требуется явно.
Miha_x64
8

Может быть, немного опоздали с игрой по этому вопросу, но в реализации синглтона есть много нюансов. Держатель рисунка нельзя использовать во многих ситуациях. И 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". Существует также реальный пример, демонстрирующий изменчивый подход с двойной проверкой. Надеюсь это поможет.

Майкл Эндрюс
источник
Не могли бы вы объяснить, почему BearerToken instanceв вашей статье нет static? И что это result.hasExpired()?
Воланд
А что class MySingleton- может быть, так и должно быть final?
Воланд
1
@Woland BearerTokenЭкземпляр не является статичным, потому что он является частью BearerTokenFactory - который настроен с определенным сервером авторизации. Может быть много BearerTokenFactoryобъектов - каждый из которых имеет свой собственный «кешированный», BearerTokenкоторый он раздает до истечения срока его действия. hasExpired()Метод на BeraerTokenназываются в фабриках get()метод , чтобы гарантировать ее не раздает терянного маркер. Если истек срок действия, новый сервер будет запрашивать новый токен. Абзац, следующий за блоком кода, объясняет это более подробно.
Майкл Эндрюс
4

Вот как реализовать простое 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();
    }
}
Николас Филотто
источник
Оба ленивы, предполагая, что единственное, что вам нужно от синглтона - это его экземпляр.
Miha_x64
@ Miha_x64, первый случай будет создавать экземпляр синглтона, когда JVM инициализирует класс, а второй случай будет создавать экземпляр только синглтона при вызове getInstance(). Но на самом деле, если у вас нет других статических методов в вашем классе, Singletonи вы только вызываете, getInstance()реальной разницы нет.
Николас Филотто
3

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

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

public static final INSTANCE == ....
private Object readResolve() {
  return INSTANCE; // original singleton instance.
} 
Onur
источник
3

Различные способы сделать одноэлементный объект:

  1. Согласно Джошуа Блоху - Enum будет лучшим.

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

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

Шайлендра Сингх
источник
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");
  }

}
Дэн Молдаван
источник
2

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

raoadnan
источник
2

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

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.

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

user3792852
источник
1

Простейший синглтон-класс

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

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

public enum Singleton{ INSTANCE; }

и все готово !!!

shikjohari
источник
1
Это уже упоминалось в других ответах лет назад.
Панг
0

Посмотрите на этот пост.

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

Из раздела «Синглтон» лучшего ответа,

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

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

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

kenju
источник
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();
    }
}

источник
-3

Иногда простого « 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;
    }

}

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

Георгий
источник
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 одновременно.

Соменат Мухопадхяй
источник