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

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

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

Marian Paździoch 06.04.2016 16:13
medium.com/@kevalpatel2106/… . This is the complete article on how to achieve thread, reflection and serialization safety in the singleton pattern. This is the good source to understand the benefits and limitations of singleton class.
Keval Patel 30.11.2016 12:20

Как указывает Джошуа Блох в «Эффективной Java», enum singleton - лучший способ. Здесь Я классифицировал различные реализации как ленивые / нетерпеливые и т. д.

isaolmez 13.04.2017 16:21
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
827
3
314 829
28
Перейти к ответу Данный вопрос помечен как решенный

Ответы 28

Убедитесь, что вам это действительно нужно. Выполните поиск в Google по запросу "одноэлементный антипаттерн", чтобы увидеть некоторые аргументы против него.

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

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

tgkprog 10.06.2013 18:05

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

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

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

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

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

Salvador Valencia 18.03.2017 00:19

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

public class A {    

    private static final A INSTANCE = new A();

    private A() {}

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

Переменная экземпляра singleton также может быть окончательной. например, частный статический финал A singleton = new A ();

jatanp 16.09.2008 15:54

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

Dan Dyer 16.09.2008 16:48

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

Tom Hawtin - tackline 16.09.2008 16:54

Я согласен с @Dan Dayer, это пример ленивой инициализации является. Если бы в классе были другие методы, тогда мог бы будет ... зависит от того, какой статический метод вызывается первым. (Представьте, что сначала вызывается public static void doSomething () в классе A - экземпляр A создается, но не используется.)

Stu Thompson 16.09.2008 18:26

почему у вас есть еще один статический метод в этом классе и вы не хотите использовать его getInstance (). Один класс Одна ответственность вне дома?

tgkprog 10.06.2013 18:03

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

Anirudhan J 20.12.2013 14:39

Я согласен, что это самый простой ответ, и Anirudhan, нет необходимости объявлять экземпляр окончательным. Никакой другой поток не получит доступа к классу, пока статические члены инициализированы. это гарантируется компилятором, другими словами, вся статическая инициализация выполняется синхронно - только один поток.

inor 03.08.2014 11:57

На самом деле это может быть простое решение, но как передать любой аргумент конструктору такого реализованного синглтона?

topr 23.04.2015 22:05

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

Jonathan 26.04.2015 11:17

У этого подхода есть одно ограничение: конструктор не может создать исключение.

wangf 26.08.2015 04:51

FindBugs жалуется на SI_INSTANCE_BEFORE_FINALS_ASSIGNED

crishushu 13.09.2016 17:16

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

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

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

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

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

Dan Dyer 16.09.2008 16:50

Для статики двойная проверка бессмысленна. И почему вы сделали защищенный метод клонирования общедоступным?

Tom Hawtin - tackline 16.09.2008 16:56

-1 ваша версия блокировки с двойной проверкой не работает.

assylias 08.08.2012 20:50

Также вам нужно сделать свою одиночную переменную volatile

MyTitle 07.03.2013 13:23

Первая версия является ленивая и потокобезопасная.

Miha_x64 09.01.2019 14:00

Потокобезопасность в 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) не гарантирует, что другие потоки увидят изменения его значения. Об этом позаботится синхронизация не - она ​​только сериализует доступ к этому блоку кода.

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

Где я могу узнать больше о модификаторе volatile?

eleven81 16.01.2009 20:16

См. Комментарии stackoverflow.com/questions/70689/…

Pascal Thivent 06.03.2010 04:34

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

luis.espinal 27.10.2010 18:16

Ключевое слово Volatile здесь не требуется, поскольку синхронизация дает как взаимное исключение, так и видимость памяти.

Hemant 18.11.2013 12:01

@Hemant: Синхронизация поверхностна, когда дело касается видимости. Синхронизация выполняется на объекте класса и не обеспечивает видимости при изменении статических членов класса. Это то же самое, что и более общая синхронизация в экземпляре, где изменяются переменные экземпляра, если явно и правильно не обрабатываются с помощью типов volatile, java.util.concurrency или чего-то еще.

Stu Thompson 18.11.2013 20:17

Есть ли простой способ защитить разработчика от прямого использования bar?

AlikElzin-kilaka 24.08.2015 14:59

Зачем возиться со всем этим в Java 5+? Насколько я понимаю, подход enum обеспечивает как потокобезопасность, так и ленивую инициализацию. Это также намного проще ... Кроме того, если вы хотите избежать перечисления, я бы все равно предотвратил подход вложенного статического класса ...

Alexandros 10.01.2016 20:26

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

Stu Thompson 11.01.2016 02:49

@Hemant: только если все потоки синхронизируются в точке синхронизации. Дело не в этом. Изменения во внутренних полях (если объект Bar не является неизменяемым и все поля являются окончательными) видны потокам, которые синхронизируются на Foo.class. Другие потоки могут видеть только некоторые изменения, особенно поток может видеть изменение ссылки, но не внутренние поля. С другой стороны, если Bar является неизменным и все его поля являются окончательными, то гарантии, предоставляемые моделью памяти jvm при назначении переменной final, гарантируют, что изменения будут видны.

Neo M Hacker 30.04.2016 04:01

«Bno» сменил имя пользователя. О каком ответе идет речь?

Peter Mortensen 07.01.2021 08:24

@PeterMortensen Benno Richters (ответ выше)

Stu Thompson 08.01.2021 20:42
Ответ принят как подходящий

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

public enum Foo {
    INSTANCE;
}

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

The Right Way to Implement a Serializable Singleton

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

Редактировать:онлайн-часть "Эффективной Java" говорит:

"This approach is functionally equivalent to the public field approach, except that it is more concise, provides the serialization machinery for free, and provides an ironclad guarantee against multiple instantiation, even in the face of sophisticated serialization or reflection attacks. While this approach has yet to be widely adopted, a single-element enum type is the best way to implement a singleton."

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

Amir Arad 07.10.2008 11:17

Вы бы использовали перечисление для «служебного» класса?

Tom Hawtin - tackline 16.07.2009 02:50

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

Stephen Denne 16.07.2009 08:01

@Tom - если он предоставляет все преимущества, предоставляемые компилятором, да, я бы стал. Перечисление - это просто класс с дополнительными ограничениями, налагаемыми компилятором и средой выполнения. Это не то же самое, что перечисления на основе целых чисел в C или Pascal.

luis.espinal 27.10.2010 18:10

Вопрос: Куда идет логика, обычно помещаемая в конструктор (т.е. однократная инициализация для синглтона)?

Eric Nguyen 24.11.2010 21:35

@Eric Вы можете создать конструктор или использовать блок инициализации.

Stephen Denne 25.11.2010 04:32

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

Ashish Sharma 18.03.2011 14:03

Если вы ищете пример этого шаблона в действии, Google Guava часто использует его для своих частных реализаций: code.google.com/p/guava-libraries/source/browse/trunk/guava/‌ src /…

Etienne Neveu 23.06.2011 16:08

@Tom, я бы также рекомендовал использовать enum для служебных классов.

Peter Lawrey 20.07.2011 09:47

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

chharvey 06.09.2011 01:28

@eneveu предоставленная вами ссылка на Google Guava дает ошибку 404.

Inquisitive 02.07.2012 14:36

@Subhra Спасибо. Стивен Денн разместил ссылку на класс Functions, и вот обновленная ссылка, которая ведет непосредственно к примеру одноэлементного шаблона перечисления в коде Guava: code.google.com/p/guava-libraries/source/browse/guava/src/co‌ m /…

Etienne Neveu 11.07.2012 02:07

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

Zombies 20.03.2013 11:11

@zombies да, может. selikoff.net/2011/06/15/java-enums-can-implement-interfaces

Ilmo Euro 01.04.2013 17:08

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

bvdb 18.08.2013 03:22

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

cHao 22.09.2013 20:43

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

Lalith B 27.02.2014 12:38

Хотя перечисление технически наиболее эффективно выполняет одноэлементный шаблон, не является ли оно также самым жестким? Что, если я решу, что мой синглтон больше не должен быть синглтоном? Если бы я использовал перечисление, мне нужно было бы провести много неприятного рефакторинга. Если бы я использовал статический получатель, он был бы гораздо более гибким, и я мог бы легко сделать его ThreadLocal и т. д.

tmn 02.10.2015 15:27

Есть ли способ реализовать это также, когда мне нужно передать некоторые параметры конструктору? Скажем, значения из конфигурации или внедрения зависимостей?

Gavriel 12.01.2016 10:21

@StephenDenne Enum поддерживается только в JDK5 и новее.

Manish Nagar 02.02.2016 09:39

Я должен не согласиться с JB здесь, потому что это Он сам, который не согласен с самим собой. Например. он говорит "не использовать интерфейс для объявления констант" и объясняет причину, по которой «постоянное использование - это не интерфейс, оно вызывает семантическое несоответствие и шум, а полученный синтаксический сахар (нет необходимости использовать static final и т. д.) не стоит того». Он использует аналогичный аргумент пару раз в EJ2 ... По аналогии, можно было бы сказать "не используйте enum для объявления класса singleton; singleton не является enum - он производит семантическое несоответствие и шум, а полученный синтаксический сахар того не стоит"

user719662 14.07.2016 13:22

Я видел этот совет в Интернете в течение многих лет. Это интересное использование enum, но семантически я просто не могу этого сделать. enum с одним значением оказывается одноэлементным, но концептуально это совсем не то, что перечисление является. И я не уверен, что мне когда-либо была нужна «железная гарантия против множественного создания экземпляров, даже перед лицом изощренных атак сериализации или отражения». :)

spaaarky21 04.05.2017 02:14

Из того, что я нашел по этому поводу, это не очень эффективно с точки зрения памяти.

Kenny Steegmans 18.06.2019 18:24

Перечисление для синглтона также правильно с точки зрения теории множеств. Класс - это бесконечный изменяемый набор объектов, определенных во время выполнения. Перечисление - это конечный неизменяемый набор объектов, определенный временем компиляции. Синглтон - это конечный неизменяемый набор объектов, определенный временем компиляции. Поэтому используйте перечисление для синглтонов. Также используйте перечисление для служебных классов. Это более формальное объяснение того, что сказал @AmirArad.

Christian Hujer 15.08.2019 21:49

@ spaaarky21 то, что вы сказали, концептуально правильно для других языков, но не для Java. В Java концептуально это и есть enumariton. Это перечисление объектов (не констант, константы для других языков), а синглтон - это перечисление ровно одного объекта.

Christian Hujer 15.08.2019 21:50

Мне кажется, что значения перечисления Java @ChristianHujer довольно постоянны. :) Я думаю, вы хотите сказать, что значения (объекты) не обязательно должны быть неизменными. static final List<Foo> FOOS = … также является постоянной ссылкой на объект, который не обязательно является неизменным. Я просто хотел сказать, что термин «перечисление» обычно подразумевает и то, и другое, даже для опытных разработчиков Java. Поскольку Java не обеспечивает неизменяемость enum (точно так же, как не заставляет FOOS быть неизменным), вы мог используете enum как способ создания синглтона тяжелого изменяемого класса. Но это просто похоже на неправильное использование концепции.

spaaarky21 20.08.2019 19:51

@ spaaarky21 Я вовсе не это хотел сказать. Я говорил не об объектах, а о типе. Набор, описываемый перечислением, неизменен. Вы не можете создать новый экземпляр перечисления во время выполнения. Но вы можете из класса просто вызвать new ClassName (), если класс каким-то образом не препятствует этому, но по умолчанию это возможно.

Christian Hujer 23.08.2019 17:40

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

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

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

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

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

Tom Anderson 06.08.2012 21:00

Чтобы немного расширить это, рассмотрим TicketNumberer, который должен иметь один глобальный экземпляр, и где вы хотите написать класс TicketIssuer, содержащий строку кода int ticketNumber = ticketNumberer.nextTicketNumber();. При традиционном мышлении синглтона предыдущая строка кода должна быть чем-то вроде TicketNumberer ticketNumberer = TicketNumberer.INSTANCE;. С точки зрения DI, класс будет иметь конструктор вроде public TicketIssuer(TicketNumberer ticketNumberer) { this.ticketNumberer = ticketNumberer; }.

Tom Anderson 06.08.2012 21:07

И вызвать этот конструктор становится чужой проблемой. Фреймворк DI сделает это с помощью какой-то глобальной карты; архитектура DI, созданная вручную, сделает это, потому что метод приложения main (или один из его миньонов) создаст зависимость, а затем вызовет конструктор. По сути, использование глобальной переменной (или глобального метода) - это просто простая форма ужасного шаблон локатора услуг, и ее можно заменить внедрением зависимостей, как и любое другое использование этого шаблона.

Tom Anderson 06.08.2012 21:09

@TomAnderson Я действительно не понимаю, почему люди «боятся» шаблона локатора сервисов. Я думаю, что в большинстве случаев это избыточно или в лучшем случае не нужно, однако есть, казалось бы, полезные случаи. С меньшим количеством параметров DI определенно предпочтительнее, но представьте 20+. Утверждение, что код не структурирован, не является верным аргументом, потому что иногда группирование параметров просто не имеет смысла. Кроме того, с точки зрения модульного тестирования, я не забочусь о тестировании службы, только о ее бизнес-логике, и если она правильно написана, это будет легко. Я видел эту потребность только в очень крупных проектах.

Brandon Ling 09.05.2018 23:17

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

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

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

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

    public static Bar getBar() {
        return BarHolder.bar;
    }
}

Справедливо! Меня просто устраивает volatile, и я его использую. О, и три ура для JCiP.

Stu Thompson 16.09.2008 18:40

О, это, по-видимому, подход, защищаемый Уильямом Пью, известным в FindBugz.

Stu Thompson 16.09.2008 18:42

@Stu Первое издание Effective Java (авторское право 2001) подробно описывает этот шаблон в пункте 48.

Pascal Thivent 06.03.2010 04:31

@Bno: А как насчет того, чтобы сделать конструктор закрытым?

xyz 18.03.2013 22:16

Если Bar требует сложной или отложенной инициализации, этот подход не сработает. Это проблематично, потому что экземпляр создается на этапе загрузки класса.

AlikElzin-kilaka 24.08.2015 15:01

@ AlikElzin-kilaka Не совсем так. Экземпляр создается на этапе загрузки класса для BarHolder, который откладывается до первой необходимости. Конструктор бара может быть сколь угодно сложным, но он не будет вызван до первого getBar(). (И если getBar называется «слишком рано», то вы столкнетесь с той же проблемой независимо от того, как реализованы синглоны.) Вы можете увидеть отложенную загрузку класса из приведенного выше кода здесь: pastebin.com/iq2eayiR

Ti Strga 09.09.2015 18:53

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

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

public enum Foo {
   INSTANCE;
}

До Java 5 наиболее простой случай:

public final class Foo {

    private static final Foo INSTANCE = new Foo();

    private Foo() {
        if (INSTANCE != null) {
            throw new IllegalStateException("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() гарантирует, что будет возвращен единственный экземпляр, даже если объект был сериализован при предыдущем запуске вашей программы.

Проверка на отражение бесполезна. Если другой код использует отражение на частных лицах, это Game Over. Нет причин даже пытаться правильно функционировать при таком неправильном использовании. И если вы попытаетесь, в любом случае это будет неполная «защита», просто много потраченного впустую кода.

Wouter Coekaerts 25.02.2009 00:42

> «Во-первых, вы хотите, чтобы урок был финальным». Может кто-нибудь уточнить это, пожалуйста?

Debajit 03.03.2009 00:09

Защита от десериализации полностью нарушена (я думаю, это упоминается во 2-м издании «Эффективная Java»).

Tom Hawtin - tackline 16.07.2009 02:45

-1 это точно нет самый простой случай, он надуманный и излишне сложный. Посмотрите на ответ Джонатана, чтобы узнать о самом простом решении, которого достаточно в 99,9% всех случаев.

Michael Borgwardt 30.09.2009 12:16

Нет. По мере того, как язык меняется и проходит время, прогрессивное понимание научило меня, что для java5 и более поздних версий лучший способ сделать это - использовать решение enum, как предлагает spdenne

Roel Spilker 08.10.2009 15:24

Вау, это намного сложнее, чем я думал. По крайней мере, метод enum дает вам больше «легкости» в таких вещах, как сериализация. А как насчет потоковой передачи?

Andriy Drozdyuk 10.08.2010 07:24

Это полезно, когда ваш синглтон должен наследовать от суперкласса. В этом случае нельзя использовать одноэлементный шаблон перечисления, поскольку перечисления не могут иметь суперкласса (хотя они могут реализовывать интерфейсы). Например, Google Guava использует статическое конечное поле, когда одноэлементный шаблон перечисления не является параметром: code.google.com/p/guava-libraries/source/browse/trunk/guava/‌ src /…

Etienne Neveu 23.06.2011 16:15

Я согласен, что пути Джонатана достаточно. Этьен, укажите на проблему, которую вы обнаружили после решения java5in Джонатана.

inor 03.08.2014 11:59

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

RvPr 31.12.2015 00:12

Да. Загрузчик классов гарантирует, что он выполняется только один раз и только при доступе к коду.

Roel Spilker 04.01.2016 18:23

Не могли бы вы использовать какой-нибудь значимый класс вместо Foo?

ericn 21.11.2016 09:27

Вам не нужно определять final класс Singleton, поскольку он фактически является окончательным, поскольку все его конструкторы являются закрытыми.

Enrico Giurin 26.01.2017 07:36

Хотя это технически правильно, я нашел его полезным по нескольким причинам: - Он документирует намерение - Он не позволяет IDE предлагать имя типа после ключевого слова extends.

Roel Spilker 26.01.2017 12:23

@eric У вас есть предложение? Elvis?

Roel Spilker 26.01.2017 12:25

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

}

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

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

Craig P. Motlin 09.01.2009 17:53

Я использую Spring Framework для управления своими одиночками.

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

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

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

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

Я бы сказал, что это одноэлементное перечисление.

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

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

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

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

1.12 Serialization of Enum Constants

Enum constants are serialized differently than ordinary serializable or externalizable objects. The serialized form of an enum constant consists solely of its name; field values of the constant are not present in the form. To serialize an enum constant, ObjectOutputStream writes the value returned by the enum constant's name method. To deserialize an enum constant, ObjectInputStream reads the constant name from the stream; the deserialized constant is then obtained by calling the java.lang.Enum.valueOf method, passing the constant's enum type along with the received constant name as arguments. Like other serializable or externalizable objects, enum constants can function as the targets of back references appearing subsequently in the serialization stream.

The process by which enum constants are serialized cannot be customized: any class-specific writeObject, readObject, readObjectNoData, writeReplace, and readResolve methods defined by enum types are ignored during serialization and deserialization. Similarly, any serialPersistentFields or serialVersionUID field declarations are also ignored--all enum types have a fixed serialVersionUID of 0L. Documenting serializable fields and data for enum types is unnecessary, since there is no variation in the type of data sent.

Quoted from Oracle documentation

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

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

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


Хорошо для чтения

  1. Шаблон Singleton
  2. Перечисления, синглтоны и десериализация
  3. Двойная проверка блокировки и шаблон Singleton

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

  1. Enum

     /**
     * Singleton pattern example using Java Enum
     */
     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 {
         // Initialized 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;
         }
     }
    

Заявление об ограничении ответственности: Я только что обобщил все замечательные ответы и написал их своими словами.


При реализации синглтона у нас есть два варианта:

  1. Ленивая загрузка
  2. Ранняя загрузка

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

Самый простой способ реализовать синглтон:

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

Это называется «идиомой двойной проверки блокировки». Легко забыть об изменчивом заявлении и трудно понять, зачем оно необходимо. Подробнее: Декларация «Двойная проверка блокировки нарушена»

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

class Foo implements Serializable {

    private static final long serialVersionUID = 1L;

    private static volatile Foo INSTANCE = null;

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

This approach is functionally equivalent to the public field approach, except that it is more concise, provides the serialization machinery for free, and provides an ironclad guarantee against multiple instantiation, even in the face of sophisticated serialization or reflection attacks. While this approach has yet to be widely adopted, a single-element enum type is the best way to implement a singleton.

-Джошуа Блох в «Эффективной Java»

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

Обновил на моем блог.

Просто пояснение: синглтоны, реализованные с помощью enum, инициализируются лениво. Подробности здесь: stackoverflow.com/questions/16771373/…

user246645 23.08.2013 11:10

отличный ответ. И последнее: переопределить метод клонирования для исключения исключения.

riship89 20.11.2014 11:01

@xyz хорошие объяснения, мне очень понравилось, я очень легко узнал, и я надеюсь, что никогда не забыл об этом

Premraj 30.06.2015 07:26

В 5-м фрагменте кода возвращаемый тип readResolve () должен быть Object.

Xavier.S 15.11.2016 14:40

Один из лучших ответов, которые я когда-либо видел на stackoverflow. Спасибо!

Shay Tsadok 14.11.2017 17:27

@ShayTsadok Рад, что смог помочь!

xyz 15.11.2017 08:06

Замечательный ответ :)

Shubh 29.10.2018 20:23

Нет никакой «ранней загрузки», поскольку JVM лениво загружает классы.

Miha_x64 09.01.2019 13:55

является - проблема сериализации с использованием перечислений в качестве синглтона: любые значения поля-члена сериализованы нет и, следовательно, не восстанавливаются. См. Спецификация сериализации объектов Java, версия 6.0. Другая проблема: нет управления версиями - все типы перечислений имеют фиксированный serialVersionUID или 0L. Третья проблема: нет настройки: любые методы writeObject, readObject, readObjectNoData, writeReplace и readResolve, специфичные для класса, определенные типами перечисления, игнорируются во время сериализации и десериализации.

Basil Bourque 24.07.2019 07:22

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

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

public class Singleton {
  private static Singleton singleInstance = new Singleton();
  private Singleton() {}
  public static Singleton getSingleInstance() {
    return singleInstance;
  }
}

это то же самое, что и ответ Джонатана ниже

inor 03.08.2014 11:53

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

Basil Bourque 03.01.2015 05:29

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.

Я не думаю, что это будет компилироваться.

Peter Mortensen 07.01.2021 08:58

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

public enum Singleton {
    INSTANCE;
}

Готово!

Об этом уже упоминалось в других ответах много лет назад.

Pang 05.02.2016 10:13

Версия 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;
    }
}

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

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

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

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

Singleton (recognizeable by creational methods returning the same instance (usually of itself) everytime)

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

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

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

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

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

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

Re Джошуа Блох: Что вы имеете в виду? Конкретная книга или сообщение в блоге? Пожалуйста, ответьте редактирование вашего ответа, а не здесь, в комментариях (без «Edit:», «Update:» или подобное - ответ должен выглядеть так, как если бы он был написан сегодня).

Peter Mortensen 07.01.2021 09:03

Одноэлементное перечисление

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

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

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

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

}

Чем это отличается от предыдущих ответов?

Peter Mortensen 07.01.2021 09:06

вы должны спросить об этом у других людей. Как видите, на этот вопрос ответили в 2015 году, это был наиболее полный ответ на тот момент :)

Dan Moldovan 29.01.2021 11:58

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

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

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

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 09.01.2019 14:02

@ Miha_x64 в первом случае создается экземпляр синглтона, когда JVM инициализирует класс, во втором случае создается экземпляр синглтона только при вызове getInstance(). Но действительно, если у вас нет других статических методов в вашем классе Singleton и вы вызываете только getInstance(), реальной разницы нет.

Nicolas Filotto 09.01.2019 14:26

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

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.

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

Есть четыре способа создать синглтон в Java.

  1. Нетерпеливый синглтон инициализации

     public class Test {
         private static final Test test = new Test();
    
         private Test() {
         }
    
         public static Test getTest() {
             return test;
         }
     }
    
  2. Синглтон с отложенной инициализацией (потокобезопасный)

     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. Синглтон Билла Пью с рисунком держателя (желательно лучший)

     public class Test {
    
         private Test() {
         }
    
         private static class TestHolder {
             private static final Test test = new Test();
         }
    
         public static Test getInstance() {
             return TestHolder.test;
         }
     }
    
  4. Одноэлементное перечисление

     public enum MySingleton {
         INSTANCE;
    
         private MySingleton() {
             System.out.println("Here");
         }
     }
    

(1) не рвется, он ленив из-за механизма загрузки классов JVM.

Miha_x64 09.01.2019 14:01

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

Dheeraj Sachan 10.01.2019 17:19

«Эффективная Java» - отличная книга, но она определенно требует редактирования.

Miha_x64 10.01.2019 20:52

@ Miha_x64, что загружается, не могли бы вы объяснить на примере

Dheeraj Sachan 10.01.2019 20:57

Делать что-то «с энтузиазмом» означает «как можно скорее». Например, Hibernate с готовностью поддерживает загрузку отношений, если это требуется явно.

Miha_x64 10.01.2019 21:00

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

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

Смотри ниже:

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

Другие вопросы по теме