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




Убедитесь, что вам это действительно нужно. Выполните поиск в Google по запросу "одноэлементный антипаттерн", чтобы увидеть некоторые аргументы против него.
Я полагаю, что в этом нет ничего плохого по сути, но это всего лишь механизм для раскрытия некоторых глобальных ресурсов / данных, поэтому убедитесь, что это лучший способ. В частности, я нашел внедрение зависимости (DI) более полезным, особенно если вы также используете модульные тесты, потому что DI позволяет вам использовать фиктивные ресурсы для целей тестирования.
вы также можете вводить фиктивные значения с помощью традиционного метода, но я предполагаю, что это не стандартный / srping способ, поэтому его дополнительная работа с усилением - это только устаревший код ...
В Википедии есть несколько Примеры синглтонов, также на Java. Реализация Java 5 выглядит довольно завершенной и ориентированной на многопоточность (применена блокировка с двойной проверкой).
Обдумайте, зачем вам нужен синглтон, прежде чем писать его. Об их использовании ведутся квазирелигиозные дебаты, о которых вы можете довольно легко споткнуться, если загуглите синглтоны на Java.
Лично я стараюсь избегать синглтонов как можно чаще по многим причинам, большинство из которых, опять же, можно найти в поисковой системе синглтонов. Я чувствую, что довольно часто синглтонами злоупотребляют, потому что их легко понять всем. Они используются как механизм для получения «глобальных» данных в объектно-ориентированном дизайне, и они используются, потому что легко обойти управление жизненным циклом объекта (или действительно думать о том, как вы можете сделать A изнутри B). Посмотрите на такие вещи, как инверсия контроля (IoC) или внедрение зависимости (DI), чтобы найти золотую середину.
Если он вам действительно нужен, то в Википедии есть хороший пример правильной реализации синглтона.
Согласовано. Это скорее базовый класс, который запускает остальную часть вашего приложения, и если он будет продублирован, вы получите полный хаос (например, единый доступ к ресурсу или обеспечение безопасности). Передача глобальных данных по всему приложению - это большой красный флаг связи. Используйте его, когда вы признаете, что вам это действительно нужно.
Забудьте о ленивая инициализация; это слишком проблематично. Это самое простое решение:
public class A {
private static final A INSTANCE = new A();
private A() {}
public static A getInstance() {
return INSTANCE;
}
}
Переменная экземпляра singleton также может быть окончательной. например, частный статический финал A singleton = new A ();
По сути, это ленивая инициализация, поскольку статический синглтон не будет создан до тех пор, пока класс не будет загружен, а класс не будет загружен до тех пор, пока он не понадобится (что будет правильным примерно в то время, когда вы впервые ссылаетесь на метод getInstance ()).
Если класс A загружается задолго до того, как вы хотите, чтобы статика была создана, вы можете обернуть статику в статический внутренний класс, чтобы отделить инициализацию класса.
Я согласен с @Dan Dayer, это пример ленивой инициализации является. Если бы в классе были другие методы, тогда мог бы будет ... зависит от того, какой статический метод вызывается первым. (Представьте, что сначала вызывается public static void doSomething () в классе A - экземпляр A создается, но не используется.)
почему у вас есть еще один статический метод в этом классе и вы не хотите использовать его getInstance (). Один класс Одна ответственность вне дома?
AFAIK, переменные экземпляра Singleton должны быть объявлены как final, поскольку это гарантирует, что переменная правильно инициализирована, прежде чем другие потоки смогут получить к ней доступ.
Я согласен, что это самый простой ответ, и Anirudhan, нет необходимости объявлять экземпляр окончательным. Никакой другой поток не получит доступа к классу, пока статические члены инициализированы. это гарантируется компилятором, другими словами, вся статическая инициализация выполняется синхронно - только один поток.
На самом деле это может быть простое решение, но как передать любой аргумент конструктору такого реализованного синглтона?
Вы не можете передавать аргументы из метода getInstance (). В любом случае я не могу придумать для этого полезного футляра. Будет ли в зависимости от аргумента несколько типов синглтонов? Если так, то это уже не синглтон.
У этого подхода есть одно ограничение: конструктор не может создать исключение.
FindBugs жалуется на SI_INSTANCE_BEFORE_FINALS_ASSIGNED
Если вам не нужна ленивая загрузка, просто попробуйте:
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 или создать реестр для всех ваших синглтонов.
Первая версия самая лучшая. Если предположить, что класс не делает ничего, кроме предоставления синглтона, то он обычно создается примерно в той же точке, что и во второй версии, из-за ленивой загрузки класса.
Для статики двойная проверка бессмысленна. И почему вы сделали защищенный метод клонирования общедоступным?
-1 ваша версия блокировки с двойной проверкой не работает.
Также вам нужно сделать свою одиночную переменную volatile
Первая версия является ленивая и потокобезопасная.
Потокобезопасность в 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?
См. Комментарии stackoverflow.com/questions/70689/…
Я думаю, что важно упомянуть об атаках отражения. Верно, что большинству разработчиков не о чем беспокоиться, но похоже, что такие примеры (по синглтонам на основе Enum) должны включать либо код, который защищает от атак с множественными экземплярами, либо просто помещать отказ от ответственности, указывающий на такие возможности.
Ключевое слово Volatile здесь не требуется, поскольку синхронизация дает как взаимное исключение, так и видимость памяти.
@Hemant: Синхронизация поверхностна, когда дело касается видимости. Синхронизация выполняется на объекте класса и не обеспечивает видимости при изменении статических членов класса. Это то же самое, что и более общая синхронизация в экземпляре, где изменяются переменные экземпляра, если явно и правильно не обрабатываются с помощью типов volatile, java.util.concurrency или чего-то еще.
Есть ли простой способ защитить разработчика от прямого использования bar?
Зачем возиться со всем этим в Java 5+? Насколько я понимаю, подход enum обеспечивает как потокобезопасность, так и ленивую инициализацию. Это также намного проще ... Кроме того, если вы хотите избежать перечисления, я бы все равно предотвратил подход вложенного статического класса ...
@Alexandros: Как отмечали другие, этот подход обеспечивает ленивую инициализацию, если это необходимо. Лично мне не особо нужен ленивый init (и большинству людей не нужен), но это действительный, поточно-безопасный одноэлементный шаблон, если он вам нужен.
@Hemant: только если все потоки синхронизируются в точке синхронизации. Дело не в этом. Изменения во внутренних полях (если объект Bar не является неизменяемым и все поля являются окончательными) видны потокам, которые синхронизируются на Foo.class. Другие потоки могут видеть только некоторые изменения, особенно поток может видеть изменение ссылки, но не внутренние поля. С другой стороны, если Bar является неизменным и все его поля являются окончательными, то гарантии, предоставляемые моделью памяти jvm при назначении переменной final, гарантируют, что изменения будут видны.
«Bno» сменил имя пользователя. О каком ответе идет речь?
@PeterMortensen Benno Richters (ответ выше)
Используйте перечисление:
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."
Я думаю, что люди должны начать рассматривать перечисления как просто класс с функцией. если вы можете перечислить экземпляры своего класса во время компиляции, используйте перечисление.
Вы бы использовали перечисление для «служебного» класса?
Лично я не часто нахожу необходимость напрямую использовать одноэлементный шаблон. Иногда я использую инъекцию зависимостей Spring с контекстом приложения, который содержит то, что он называет синглтонами. Мои служебные классы обычно содержат только статические методы, и мне не нужны их экземпляры.
@Tom - если он предоставляет все преимущества, предоставляемые компилятором, да, я бы стал. Перечисление - это просто класс с дополнительными ограничениями, налагаемыми компилятором и средой выполнения. Это не то же самое, что перечисления на основе целых чисел в C или Pascal.
Вопрос: Куда идет логика, обычно помещаемая в конструктор (т.е. однократная инициализация для синглтона)?
@Eric Вы можете создать конструктор или использовать блок инициализации.
Привет, Кто-нибудь может сказать мне, как этот тип синглтона можно смоделировать и протестировать в тестовых примерах. Я попытался заменить поддельный экземпляр синглтона на этот тип, но не смог.
Если вы ищете пример этого шаблона в действии, Google Guava часто использует его для своих частных реализаций: code.google.com/p/guava-libraries/source/browse/trunk/guava/ src /…
@Tom, я бы также рекомендовал использовать enum для служебных классов.
Думаю, это имеет смысл, но мне все равно это не нравится. Как бы вы создали синглтон, расширяющий другой класс? Если вы используете перечисление, вы не можете.
@eneveu предоставленная вами ссылка на Google Guava дает ошибку 404.
@Subhra Спасибо. Стивен Денн разместил ссылку на класс Functions, и вот обновленная ссылка, которая ведет непосредственно к примеру одноэлементного шаблона перечисления в коде Guava: code.google.com/p/guava-libraries/source/browse/guava/src/co m /…
Это не одноэлементный шаблон проектирования, поскольку он не может реализовывать интерфейсы. Это больше похоже на «как я могу иметь только одно выделение структуры (которая также может иметь указатели на функции) в c».
@zombies да, может. selikoff.net/2011/06/15/java-enums-can-implement-interfaces
Вы никогда не сможете использовать суперкласс, определить конструктор, использовать внешнюю фабричную функцию или отменить ее. Это умное решение, позволяющее сэкономить 7 строк кода, жертвуя при этом бесценной гибкостью.
@bvdb: Если вам нужна большая гибкость, вы уже напортачили, реализовав синглтон. Возможность создать независимый экземпляр, когда вам это нужно, сама по себе бесценна.
ну, безусловно, это лучший метод реализации синглтона, может быть другой парень пытается отредактировать ваш код и, не зная об этом, как синглтон, может создать новый объект класса и использовать его, как любой другой нормальный класс. такой подход позволяет нам не создавать объект, поэтому это лучший метод.
Хотя перечисление технически наиболее эффективно выполняет одноэлементный шаблон, не является ли оно также самым жестким? Что, если я решу, что мой синглтон больше не должен быть синглтоном? Если бы я использовал перечисление, мне нужно было бы провести много неприятного рефакторинга. Если бы я использовал статический получатель, он был бы гораздо более гибким, и я мог бы легко сделать его ThreadLocal и т. д.
Есть ли способ реализовать это также, когда мне нужно передать некоторые параметры конструктору? Скажем, значения из конфигурации или внедрения зависимостей?
@StephenDenne Enum поддерживается только в JDK5 и новее.
Я должен не согласиться с JB здесь, потому что это Он сам, который не согласен с самим собой. Например. он говорит "не использовать интерфейс для объявления констант" и объясняет причину, по которой «постоянное использование - это не интерфейс, оно вызывает семантическое несоответствие и шум, а полученный синтаксический сахар (нет необходимости использовать static final и т. д.) не стоит того». Он использует аналогичный аргумент пару раз в EJ2 ... По аналогии, можно было бы сказать "не используйте enum для объявления класса singleton; singleton не является enum - он производит семантическое несоответствие и шум, а полученный синтаксический сахар того не стоит"
Я видел этот совет в Интернете в течение многих лет. Это интересное использование enum, но семантически я просто не могу этого сделать. enum с одним значением оказывается одноэлементным, но концептуально это совсем не то, что перечисление является. И я не уверен, что мне когда-либо была нужна «железная гарантия против множественного создания экземпляров, даже перед лицом изощренных атак сериализации или отражения». :)
Из того, что я нашел по этому поводу, это не очень эффективно с точки зрения памяти.
Перечисление для синглтона также правильно с точки зрения теории множеств. Класс - это бесконечный изменяемый набор объектов, определенных во время выполнения. Перечисление - это конечный неизменяемый набор объектов, определенный временем компиляции. Синглтон - это конечный неизменяемый набор объектов, определенный временем компиляции. Поэтому используйте перечисление для синглтонов. Также используйте перечисление для служебных классов. Это более формальное объяснение того, что сказал @AmirArad.
@ spaaarky21 то, что вы сказали, концептуально правильно для других языков, но не для Java. В Java концептуально это и есть enumariton. Это перечисление объектов (не констант, константы для других языков), а синглтон - это перечисление ровно одного объекта.
Мне кажется, что значения перечисления Java @ChristianHujer довольно постоянны. :) Я думаю, вы хотите сказать, что значения (объекты) не обязательно должны быть неизменными. static final List<Foo> FOOS = … также является постоянной ссылкой на объект, который не обязательно является неизменным. Я просто хотел сказать, что термин «перечисление» обычно подразумевает и то, и другое, даже для опытных разработчиков Java. Поскольку Java не обеспечивает неизменяемость enum (точно так же, как не заставляет FOOS быть неизменным), вы мог используете enum как способ создания синглтона тяжелого изменяемого класса. Но это просто похоже на неправильное использование концепции.
@ spaaarky21 Я вовсе не это хотел сказать. Я говорил не об объектах, а о типе. Набор, описываемый перечислением, неизменен. Вы не можете создать новый экземпляр перечисления во время выполнения. Но вы можете из класса просто вызвать new ClassName (), если класс каким-то образом не препятствует этому, но по умолчанию это возможно.
Меня озадачивают некоторые ответы, в которых предлагается внедрение зависимости (DI) в качестве альтернативы использованию синглтонов; это не связанные между собой концепции. Вы можете использовать DI для внедрения одноэлементных или не одноэлементных экземпляров (например, для каждого потока). По крайней мере, это правда, если вы используете Spring 2.x, я не могу говорить о других фреймворках DI.
Итак, мой ответ на OP будет (во всем, кроме самого тривиального примера кода):
Этот подход дает вам красивую развязанную (и, следовательно, гибкую и тестируемую) архитектуру, где вопрос об использовании синглтона является легко обратимой деталью реализации (при условии, что любые синглтоны, которые вы используете, являются потокобезопасными).
Возможно, потому, что люди с тобой не согласны. Я не голосовал против вас, но не согласен: я думаю, что DI можно использовать для решения тех же проблем, что и синглтоны. Это основано на понимании «синглтона» как «объекта с одним экземпляром, доступ к которому осуществляется напрямую по глобальному имени», а не просто «объекта с одним экземпляром», что, возможно, немного сложно.
Чтобы немного расширить это, рассмотрим TicketNumberer, который должен иметь один глобальный экземпляр, и где вы хотите написать класс TicketIssuer, содержащий строку кода int ticketNumber = ticketNumberer.nextTicketNumber();. При традиционном мышлении синглтона предыдущая строка кода должна быть чем-то вроде TicketNumberer ticketNumberer = TicketNumberer.INSTANCE;. С точки зрения DI, класс будет иметь конструктор вроде public TicketIssuer(TicketNumberer ticketNumberer) { this.ticketNumberer = ticketNumberer; }.
И вызвать этот конструктор становится чужой проблемой. Фреймворк DI сделает это с помощью какой-то глобальной карты; архитектура DI, созданная вручную, сделает это, потому что метод приложения main (или один из его миньонов) создаст зависимость, а затем вызовет конструктор. По сути, использование глобальной переменной (или глобального метода) - это просто простая форма ужасного шаблон локатора услуг, и ее можно заменить внедрением зависимостей, как и любое другое использование этого шаблона.
@TomAnderson Я действительно не понимаю, почему люди «боятся» шаблона локатора сервисов. Я думаю, что в большинстве случаев это избыточно или в лучшем случае не нужно, однако есть, казалось бы, полезные случаи. С меньшим количеством параметров DI определенно предпочтительнее, но представьте 20+. Утверждение, что код не структурирован, не является верным аргументом, потому что иногда группирование параметров просто не имеет смысла. Кроме того, с точки зрения модульного тестирования, я не забочусь о тестировании службы, только о ее бизнес-логике, и если она правильно написана, это будет легко. Я видел эту потребность только в очень крупных проектах.
Решение, опубликованное Стю Томпсоном действителен в 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.
О, это, по-видимому, подход, защищаемый Уильямом Пью, известным в FindBugz.
@Stu Первое издание Effective Java (авторское право 2001) подробно описывает этот шаблон в пункте 48.
@Bno: А как насчет того, чтобы сделать конструктор закрытым?
Если Bar требует сложной или отложенной инициализации, этот подход не сработает. Это проблематично, потому что экземпляр создается на этапе загрузки класса.
@ AlikElzin-kilaka Не совсем так. Экземпляр создается на этапе загрузки класса для BarHolder, который откладывается до первой необходимости. Конструктор бара может быть сколь угодно сложным, но он не будет вызван до первого getBar(). (И если getBar называется «слишком рано», то вы столкнетесь с той же проблемой независимо от того, как реализованы синглоны.) Вы можете увидеть отложенную загрузку класса из приведенного выше кода здесь: pastebin.com/iq2eayiR
В зависимости от использования есть несколько «правильных» ответов.
Начиная с 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. Нет причин даже пытаться правильно функционировать при таком неправильном использовании. И если вы попытаетесь, в любом случае это будет неполная «защита», просто много потраченного впустую кода.
> «Во-первых, вы хотите, чтобы урок был финальным». Может кто-нибудь уточнить это, пожалуйста?
Защита от десериализации полностью нарушена (я думаю, это упоминается во 2-м издании «Эффективная Java»).
-1 это точно нет самый простой случай, он надуманный и излишне сложный. Посмотрите на ответ Джонатана, чтобы узнать о самом простом решении, которого достаточно в 99,9% всех случаев.
Нет. По мере того, как язык меняется и проходит время, прогрессивное понимание научило меня, что для java5 и более поздних версий лучший способ сделать это - использовать решение enum, как предлагает spdenne
Вау, это намного сложнее, чем я думал. По крайней мере, метод enum дает вам больше «легкости» в таких вещах, как сериализация. А как насчет потоковой передачи?
Это полезно, когда ваш синглтон должен наследовать от суперкласса. В этом случае нельзя использовать одноэлементный шаблон перечисления, поскольку перечисления не могут иметь суперкласса (хотя они могут реализовывать интерфейсы). Например, Google Guava использует статическое конечное поле, когда одноэлементный шаблон перечисления не является параметром: code.google.com/p/guava-libraries/source/browse/trunk/guava/ src /…
Я согласен, что пути Джонатана достаточно. Этьен, укажите на проблему, которую вы обнаружили после решения java5in Джонатана.
Мне нравится ваше решение для отложенной инициализации с использованием FooLoader. Можно ли использовать этот шаблон для обеспечения того, чтобы определенный блок кода запускался только один раз в ленивом режиме? См. Мой ответ на это обсуждение здесь. Очень хотелось бы получить ваши отзывы по этому вопросу.
Да. Загрузчик классов гарантирует, что он выполняется только один раз и только при доступе к коду.
Не могли бы вы использовать какой-нибудь значимый класс вместо Foo?
Вам не нужно определять final класс Singleton, поскольку он фактически является окончательным, поскольку все его конструкторы являются закрытыми.
Хотя это технически правильно, я нашел его полезным по нескольким причинам: - Он документирует намерение - Он не позволяет IDE предлагать имя типа после ключевого слова extends.
@eric У вас есть предложение? Elvis?
Иногда простого "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;
}
}
Что теперь происходит? Класс загружается через загрузчик классов. Сразу после интерпретации класса из байтового массива виртуальная машина выполняет блок статический {}. вот и весь секрет: статический блок вызывается только один раз, когда данный класс (имя) данного пакета загружается этим одним загрузчиком классов.
Не правда. статические переменные инициализируются вместе со статическими блоками при загрузке класса. Не нужно разделять декларацию.
Я использую 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,
ObjectOutputStreamwrites the value returned by the enum constant's name method. To deserialize an enum constant,ObjectInputStreamreads the constant name from the stream; the deserialized constant is then obtained by calling thejava.lang.Enum.valueOfmethod, 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, andreadResolvemethods defined by enum types are ignored during serialization and deserialization. Similarly, anyserialPersistentFieldsorserialVersionUIDfield declarations are also ignored--all enum types have a fixedserialVersionUIDof0L. Documenting serializable fields and data for enum types is unnecessary, since there is no variation in the type of data sent.
Другая проблема обычных синглтонов заключается в том, что после реализации интерфейса Serializable они больше не остаются синглетонами, потому что метод readObject() всегда возвращает новый экземпляр, как конструктор в Java. Этого можно избежать, используя readResolve() и отказавшись от вновь созданного экземпляра, заменив его синглтоном, как показано ниже:
// readResolve to prevent another instance of Singleton
private Object readResolve(){
return INSTANCE;
}
Это может стать еще более сложным, если ваш одноэлементный класс поддерживает состояние, так как вам нужно сделать их временными, но в одноэлементном перечислении сериализация гарантируется JVM.
Хорошо для чтения
Ниже приведены три разных подхода.
Enum
/**
* Singleton pattern example using Java Enum
*/
public enum EasySingleton {
INSTANCE;
}
Двойная проверка блокировки / отложенная загрузка
/**
* 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;
}
}
Статический заводской метод
/**
* 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;
}
}
Заявление об ограничении ответственности: Я только что обобщил все замечательные ответы и написал их своими словами.
При реализации синглтона у нас есть два варианта:
Ленивая загрузка увеличивает накладные расходы (много, если честно), поэтому используйте ее только тогда, когда у вас есть очень большой объект или тяжелый код конструкции и также имеет другие доступные статические методы или поля, которые могут использоваться до того, как потребуется экземпляр, тогда и только тогда вам нужно использовать ленивую инициализацию. В противном случае хорошим выбором будет ранняя загрузка.
Самый простой способ реализовать синглтон:
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/…
отличный ответ. И последнее: переопределить метод клонирования для исключения исключения.
@xyz хорошие объяснения, мне очень понравилось, я очень легко узнал, и я надеюсь, что никогда не забыл об этом
В 5-м фрагменте кода возвращаемый тип readResolve () должен быть Object.
Один из лучших ответов, которые я когда-либо видел на stackoverflow. Спасибо!
@ShayTsadok Рад, что смог помочь!
Замечательный ответ :)
Нет никакой «ранней загрузки», поскольку JVM лениво загружает классы.
является - проблема сериализации с использованием перечислений в качестве синглтона: любые значения поля-члена сериализованы нет и, следовательно, не восстанавливаются. См. Спецификация сериализации объектов Java, версия 6.0. Другая проблема: нет управления версиями - все типы перечислений имеют фиксированный serialVersionUID или 0L. Третья проблема: нет настройки: любые методы writeObject, readObject, readObjectNoData, writeReplace и readResolve, специфичные для класса, определенные типами перечисления, игнорируются во время сериализации и десериализации.
Для JSE 5.0 и выше используйте подход Enum. В противном случае используйте подход статического одноэлементного держателя ((подход с отложенной загрузкой, описанный Биллом Пью). Последнее решение также является поточно-ориентированным и не требует специальных языковых конструкций (т. Е. Изменчивых или синхронизированных).
Простейший одноэлементный класс:
public class Singleton {
private static Singleton singleInstance = new Singleton();
private Singleton() {}
public static Singleton getSingleInstance() {
return singleInstance;
}
}
это то же самое, что и ответ Джонатана ниже
Дубликат этот ответ родного брата авторства Джонатан размещен пятью годами ранее. См. Этот ответ для получения интересных комментариев.
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.
Я не думаю, что это будет компилироваться.
Я по-прежнему думаю, что после Java 1.5 enum является лучшей доступной реализацией singleton, поскольку она также гарантирует, что даже в многопоточных средах создается только один экземпляр.
public enum Singleton {
INSTANCE;
}
Готово!
Об этом уже упоминалось в других ответах много лет назад.
Версия 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.
Различные способы сделать одноэлементный объект:
Согласно Джошуа Блох - Enum будет лучшим.
Вы также можете использовать блокировку с двойной проверкой.
Можно использовать даже внутренний статический класс.
Re Джошуа Блох: Что вы имеете в виду? Конкретная книга или сообщение в блоге? Пожалуйста, ответьте редактирование вашего ответа, а не здесь, в комментариях (без «Edit:», «Update:» или подобное - ответ должен выглядеть так, как если бы он был написан сегодня).
Одноэлементное перечисление
Самый простой способ реализовать синглтон, который является потокобезопасным, - использовать 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");
}
}
Чем это отличается от предыдущих ответов?
вы должны спросить об этом у других людей. Как видите, на этот вопрос ответили в 2015 году, это был наиболее полный ответ на тот момент :)
Вот как реализовать простой одиночка:
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 в первом случае создается экземпляр синглтона, когда JVM инициализирует класс, во втором случае создается экземпляр синглтона только при вызове getInstance(). Но действительно, если у вас нет других статических методов в вашем классе Singleton и вы вызываете только getInstance(), реальной разницы нет.
Другой аргумент, часто используемый против синглтонов, - их проблемы с тестируемостью. Синглтоны нелегко смоделировать в целях тестирования. Если это окажется проблемой, я хотел бы внести следующие небольшие изменения:
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.
Нетерпеливый синглтон инициализации
public class Test {
private static final Test test = new Test();
private Test() {
}
public static Test getTest() {
return test;
}
}
Синглтон с отложенной инициализацией (потокобезопасный)
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;
}
}
Синглтон Билла Пью с рисунком держателя (желательно лучший)
public class Test {
private Test() {
}
private static class TestHolder {
private static final Test test = new Test();
}
public static Test getInstance() {
return TestHolder.test;
}
}
Одноэлементное перечисление
public enum MySingleton {
INSTANCE;
private MySingleton() {
System.out.println("Here");
}
}
(1) не рвется, он ленив из-за механизма загрузки классов JVM.
@ Miha_x64 когда я сказал нетерпеливую загрузку, я сказал нетерпеливую инициализацию. Если вы думаете, что и то и другое одинаково, то то, что есть нетерпеливой загрузкой. Может быть, вам стоит написать книгу и исправить ошибки, допущенные предыдущими авторами, такими как Джошуа Блох.
«Эффективная Java» - отличная книга, но она определенно требует редактирования.
@ Miha_x64, что загружается, не могли бы вы объяснить на примере
Делать что-то «с энтузиазмом» означает «как можно скорее». Например, Hibernate с готовностью поддерживает загрузку отношений, если это требуется явно.
Лучший синглтон-шаблон, который я когда-либо видел, использует интерфейс поставщика.
Смотри ниже:
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();
}
}
«Каков эффективный способ реализации одноэлементного шаблона в Java?» пожалуйста, определите эффективный.