Каков эффективный способ реализации одноэлементного шаблона в Java?
java
singleton
design-patterns
Рияз Мохаммед Ибрагим
источник
источник
Ответы:
Используйте перечисление:
Джошуа Блох объяснил этот подход в своем выступлении « Эффективная Java Reloaded» на Google I / O 2008: ссылка на видео . Также см. Слайды 30-32 его презентации ( ffective_java_reloaded.pdf ):
Edit: онлайн часть «Эффективное Java» , говорит:
источник
В зависимости от использования, есть несколько «правильных» ответов.
Начиная с java5, лучший способ сделать это - использовать enum:
До java5, самый простой случай:
Давайте пройдемся по коду. Во-первых, вы хотите, чтобы урок был окончательным. В этом случае я использовал
final
ключевое слово, чтобы пользователи знали, что оно является окончательным. Затем вам нужно сделать конструктор закрытым, чтобы пользователи не могли создавать свои собственные Foo. Создание исключения из конструктора не позволяет пользователям использовать отражение для создания второго Foo. Затем вы создаетеprivate static final Foo
поле для хранения единственного экземпляра иpublic static Foo getInstance()
метод для его возврата. Спецификация Java гарантирует, что конструктор вызывается только при первом использовании класса.Если у вас очень большой объект или код тяжелой конструкции И также есть другие доступные статические методы или поля, которые могут быть использованы до того, как понадобится экземпляр, тогда и только тогда вам нужно использовать отложенную инициализацию.
Вы можете использовать
private static class
для загрузки экземпляра. Код будет выглядеть так:Поскольку строка
private static final Foo INSTANCE = new Foo();
выполняется только тогда, когда класс FooLoader фактически используется, это заботится о отложенной реализации и гарантирует, что она будет поточно-ориентированной.Если вы также хотите иметь возможность сериализации вашего объекта, вам нужно убедиться, что десериализация не создаст копию.
Метод
readResolve()
гарантирует, что единственный экземпляр будет возвращен, даже если объект был сериализован в предыдущем запуске вашей программы.источник
Отказ от ответственности: я только что суммировал все удивительные ответы и написал это в моих словах.
При реализации Singleton у нас есть 2 варианта
1. Ленивая загрузка
2. Ранняя загрузка
Ленивая загрузка добавляет немного накладных расходов (много, если честно), поэтому используйте его только тогда, когда у вас есть очень большой объект или код тяжелой конструкции, а также есть другие доступные статические методы или поля, которые могут быть использованы до того, как потребуется экземпляр, тогда и только тогда вам нужно использовать ленивую инициализацию. В противном случае выбор ранней загрузки - это хороший выбор.
Самый простой способ реализации Singleton это
Все хорошо, кроме его рано загруженного синглтона. Давайте попробуем ленивый загруженный синглтон
Пока все хорошо, но наш герой не выживет, сражаясь в одиночку с множеством злых нитей, которые хотят много-много экземпляров нашего героя. Итак, давайте защитим его от зла
но этого недостаточно, чтобы защитить героя, действительно !!! Это лучшее, что мы можем / должны сделать, чтобы помочь нашему герою
Это называется «идиома блокировки с двойной проверкой». Легко забыть изменчивое утверждение и трудно понять, почему это необходимо.
Для получения дополнительной информации: http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
Теперь мы уверены в злой нити, но как насчет жестокой сериализации? Мы должны убедиться, что даже при десериализации не создается новый объект
Метод
readResolve()
будет гарантировать, что единственный экземпляр будет возвращен, даже если объект был сериализован в предыдущем запуске нашей программы.Наконец, мы добавили достаточную защиту от потоков и сериализации, но наш код выглядит громоздким и безобразным. Давайте передадим нашему герою
Да, это наш тот же самый герой :)
Так как строка
private static final Foo INSTANCE = new Foo();
выполняется только тогда, когда классFooLoader
фактически используется, это заботится о ленивом экземпляре,и гарантированно ли это потокобезопасность.
И мы зашли так далеко, вот лучший способ достичь всего, что мы сделали, это наилучший способ
Который внутренне будет рассматриваться как
Это оно! Больше нет страха перед сериализацией, потоками и уродливым кодом. Также ENUMS синглтон лениво инициализируется .
Джошуа Блох в "Эффективной Яве"
Теперь вы, возможно, поняли, почему ENUMS считаются лучшим способом реализации Singleton, и спасибо за ваше терпение :)
Обновил его в моем блоге .
источник
serialVersionUID
из0L
. Третья проблема: нет настройки. Любые специфичные для класса методы writeObject, readObject, readObjectNoData, writeReplace и readResolve, определенные типами enum, игнорируются во время сериализации и десериализации.Решение, опубликованное Стю Томпсоном, действительно в Java5.0 и более поздних версиях. Но я бы предпочел не использовать его, потому что я думаю, что он подвержен ошибкам.
Легко забыть изменчивое утверждение и трудно понять, почему это необходимо. Без volatile этот код больше не был бы безопасен для потоков благодаря двойной проверке антипаттерна блокировки. Подробнее об этом см. В параграфе 16.2.4 Java-параллелизма на практике . Вкратце: этот шаблон (до Java5.0 или без оператора volatile) может возвращать ссылку на объект Bar, который (все еще) находится в неправильном состоянии.
Этот шаблон был изобретен для оптимизации производительности. Но это больше не является реальной проблемой. Следующий ленивый код инициализации является быстрым и, что более важно, легче читаемым.
источник
getBar()
. (И еслиgetBar
он называется «слишком рано», то вы столкнетесь с одной и той же проблемой, независимо от того, как реализованы синглоны.) Вы можете увидеть ленивую загрузку классов приведенного выше кода здесь: pastebin.com/iq2eayiRПотокобезопасен в Java 5+:
РЕДАКТИРОВАТЬ : обратите внимание на
volatile
модификатор здесь. :) Это важно, потому что без него JMM (модель памяти Java) не гарантирует другие потоки, чтобы увидеть изменения в его значении. Синхронизация не заботится об этом - она только сериализует доступ к этому блоку кода.РЕДАКТИРОВАТЬ 2 : Ответ @Bno подробно описывает подход, рекомендованный Биллом Пью (FindBugs), и, возможно, лучше. Читайте и голосуйте за его ответ тоже.
источник
Забудьте о отложенной инициализации , это слишком проблематично. Это самое простое решение:
источник
Убедитесь, что вам это действительно нужно. Сделайте гугл для "единственного анти-образца", чтобы видеть некоторые аргументы против этого. Полагаю, в этом нет ничего плохого, но это всего лишь механизм раскрытия некоторых глобальных ресурсов / данных, поэтому убедитесь, что это лучший способ. В частности, я нашел внедрение зависимостей более полезным, особенно если вы также используете модульные тесты, потому что DI позволяет вам использовать фиктивные ресурсы для целей тестирования.
источник
Я озадачен некоторыми ответами, которые предлагают DI в качестве альтернативы использованию синглетонов; это не связанные понятия. Вы можете использовать DI для внедрения одноэлементных или не-одноэлементных (например, для каждого потока) экземпляров. По крайней мере, это так, если вы используете Spring 2.x, я не могу говорить о других платформах DI.
Таким образом, мой ответ на OP будет (во всем, кроме самого простого примера кода):
Этот подход дает вам хорошую развязанную (и, следовательно, гибкую и тестируемую) архитектуру, в которой использование одиночного кода является легко обратимой деталью реализации (при условии, что любые используемые вами одиночные пакеты, конечно, безопасны для потоков).
источник
TicketNumberer
который должен иметь один глобальный экземпляр, и где вы хотите написать класс,TicketIssuer
содержащий строку кодаint ticketNumber = ticketNumberer.nextTicketNumber();
. В традиционном одноэлементном мышлении предыдущая строка кода должна выглядеть примерно такTicketNumberer ticketNumberer = TicketNumberer.INSTANCE;
. В DI мышлении, класс будет иметь конструктор, какpublic TicketIssuer(TicketNumberer ticketNumberer) { this.ticketNumberer = ticketNumberer; }
.main
метод приложения (или один из его миньонов) создаст зависимость и затем вызовет конструктор. По сути, использование глобальной переменной (или глобального метода) является простой формой страшного шаблона локатора службы и может быть заменено внедрением зависимости, как и любое другое использование этого шаблона.Действительно подумайте, зачем вам нужен синглтон, прежде чем писать. Существуют квазирелигиозные дебаты об их использовании, о которых вы можете легко споткнуться, если будете гуглить одиночные игры на Java.
Лично я стараюсь избегать синглетонов как можно чаще по многим причинам, опять же, большинство из которых можно найти, прибегая к помощи синглетонов. Я чувствую, что нередко злоупотребляют синглетами, потому что их легко понять всем, они используются в качестве механизма для передачи «глобальных» данных в ОО-дизайн и используются потому, что легко обойти управление жизненным циклом объекта (или действительно думать о том, как вы можете сделать изнутри B). Посмотрите на такие вещи, как Inversion of Control (IoC) или Dependency Injection (DI) для хорошего среднего уровня.
Если вам это действительно нужно, то в Википедии есть хороший пример правильной реализации синглтона.
источник
Ниже приведены 3 разных подхода
1) Enum
2) Дважды проверил Блокировка / Ленивая загрузка
3) Статический фабричный метод
источник
Я использую Spring Framework для управления своими синглетонами. Он не обеспечивает «одноэлементность» класса (что на самом деле невозможно, если задействовано несколько загрузчиков классов), но обеспечивает действительно простой способ создания и настройки различных фабрик для создания объектов разных типов.
источник
В Википедии есть несколько примеров синглетонов, в том числе и на Java. Реализация Java 5 выглядит довольно полной и поточно-ориентированной (применяется двойная проверка блокировки).
источник
Версия 1:
Ленивая загрузка, потокобезопасность с блокировкой, низкая производительность из-за
synchronized
.Версия 2:
Ленивая загрузка, потокобезопасный с неблокирующим, высокая производительность.
источник
Если вам не нужна ленивая загрузка, просто попробуйте
Если вы хотите ленивую загрузку и хотите, чтобы ваш Singleton был безопасен для потоков, попробуйте шаблон двойной проверки
Поскольку шаблон двойной проверки не гарантированно сработает (из-за некоторых проблем с компиляторами, я ничего не знаю об этом.), Вы также можете попробовать синхронизировать весь метод getInstance или создать реестр для всех ваших синглетонов.
источник
volatile
Я бы сказал, Enum Singleton
Синглтон, использующий enum в Java, - это, как правило, способ объявить синглтон enum. Enum singleton может содержать переменную экземпляра и метод экземпляра. Для простоты также обратите внимание, что если вы используете какой-либо метод экземпляра, вам нужно обеспечить безопасность потоков этого метода, если это вообще повлияет на состояние объекта.
Использование enum очень просто реализовать и не имеет недостатков в отношении сериализуемых объектов, которые необходимо обойти другими способами.
Вы можете получить к нему доступ
Singleton.INSTANCE
гораздо проще, чем вызыватьgetInstance()
метод в Singleton.Другая проблема с обычными синглетонами заключается в том, что после реализации
Serializable
интерфейса они больше не остаются синглтонами, потому чтоreadObject()
метод всегда возвращает новый экземпляр, такой как конструктор, в Java. Этого можно избежать, используяreadResolve()
и удаляя вновь созданный экземпляр, заменив его на синглтон, как показано нижеЭто может стать еще более сложным, если ваш класс Singleton поддерживает состояние, так как вам нужно сделать его временным, но в Enum Singleton сериализация гарантируется JVM.
Хорошо читать
источник
источник
Может быть, немного опоздали с игрой по этому вопросу, но в реализации синглтона есть много нюансов. Держатель рисунка нельзя использовать во многих ситуациях. И IMO при использовании volatile - вы также должны использовать локальную переменную. Давайте начнем с самого начала и повторим проблему. Вы поймете, что я имею в виду.
Первая попытка может выглядеть примерно так:
Здесь у нас есть класс MySingleton, который имеет закрытый статический член с именем INSTANCE и открытый статический метод с именем getInstance (). При первом вызове getInstance () член INSTANCE имеет значение null. Затем поток перейдет в состояние создания и создаст новый экземпляр класса MySingleton. Последующие вызовы getInstance () обнаружат, что переменная INSTANCE уже установлена, и, следовательно, не создадут другой экземпляр MySingleton. Это гарантирует, что существует только один экземпляр MySingleton, который используется всеми вызывающими getInstance ().
Но у этой реализации есть проблема. Многопоточные приложения будут иметь условие гонки при создании единственного экземпляра. Если несколько потоков выполнения попадут в метод getInstance () одновременно (или около того), каждый из них увидит элемент INSTANCE как нулевой. Это приведет к тому, что каждый поток создаст новый экземпляр MySingleton, а затем установит элемент INSTANCE.
Здесь мы использовали ключевое слово synchronized в сигнатуре метода для синхронизации метода getInstance (). Это, безусловно, исправит наше состояние гонки. Теперь потоки будут блокироваться и вводить метод по одному. Но это также создает проблему производительности. Эта реализация не только синхронизирует создание отдельного экземпляра, но и все вызовы getInstance (), включая чтение. Чтения не должны быть синхронизированы, поскольку они просто возвращают значение INSTANCE. Поскольку чтение будет составлять основную часть наших вызовов (помните, что создание экземпляров происходит только при первом вызове), мы подвергнемся ненужному снижению производительности при синхронизации всего метода.
Здесь мы переместили синхронизацию из сигнатуры метода в синхронизированный блок, который оборачивает создание экземпляра MySingleton. Но решает ли это нашу проблему? Что ж, мы больше не блокируем чтения, но мы также сделали шаг назад. Несколько потоков попадут в метод getInstance () в одно и то же время или около того, и все они увидят элемент INSTANCE как нулевой. Затем они попадут в синхронизированный блок, где можно получить блокировку и создать экземпляр. Когда этот поток выходит из блока, другие потоки будут бороться за блокировку, и один за другим каждый поток будет проходить через блок и создавать новый экземпляр нашего класса. Итак, мы вернулись туда, откуда начали.
Здесь мы выдаем еще одну проверку изнутри блока. Если элемент INSTANCE уже установлен, мы пропустим инициализацию. Это называется двойной проверкой блокировки.
Это решает нашу проблему множественной реализации. Но еще раз, наше решение поставило еще одну проблему. Другие темы могут не «видеть», что участник INSTANCE обновлен. Это из-за того, как Java оптимизирует операции с памятью. Потоки копируют исходные значения переменных из основной памяти в кэш процессора. Изменения значений затем записываются в этот кэш и считываются из него. Это особенность Java, предназначенная для оптимизации производительности. Но это создает проблему для нашей одноэлементной реализации. Второй поток, обрабатываемый другим процессором или ядром с использованием другого кэша, не увидит изменений, внесенных первым. Это приведет к тому, что второй поток увидит член INSTANCE как ноль, что приведет к созданию нового экземпляра нашего синглтона.
Мы решаем это, используя ключевое слово volatile в объявлении члена INSTANCE. Это скажет компилятору всегда читать и записывать в основную память, а не в кэш процессора.
Но это простое изменение имеет свою цену. Поскольку мы обходим кэш-память ЦП, мы будем снижать производительность каждый раз, когда работаем с изменчивым членом INSTANCE, что мы делаем 4 раза. Мы дважды проверяем существование (1 и 2), устанавливаем значение (3), а затем возвращаем значение (4). Можно утверждать, что этот путь является крайним случаем, поскольку мы создаем экземпляр только во время первого вызова метода. Возможно, удар по производительности на создание терпимо. Но даже наш основной вариант использования, читает, будет работать на volatile член дважды. Один раз проверить существование, и снова вернуть его значение.
Поскольку снижение производительности связано с работой непосредственно на volatile-члене, давайте установим локальную переменную в значение volatile и вместо этого будем работать с локальной переменной. Это уменьшит количество раз, когда мы работаем с энергозависимым, тем самым восстанавливая некоторые из наших потерянных показателей. Обратите внимание, что мы должны снова установить нашу локальную переменную при входе в синхронизированный блок. Это гарантирует, что он в курсе любых изменений, которые произошли, пока мы ожидали блокировки.
Я недавно написал статью об этом. Деконструкция Синглтона . Вы можете найти больше информации об этих примерах и пример паттерна "holder". Существует также реальный пример, демонстрирующий изменчивый подход с двойной проверкой. Надеюсь это поможет.
источник
BearerToken instance
в вашей статье нетstatic
? И что этоresult.hasExpired()
?class MySingleton
- может быть, так и должно бытьfinal
?BearerToken
Экземпляр не является статичным, потому что он является частьюBearerTokenFactory
- который настроен с определенным сервером авторизации. Может быть многоBearerTokenFactory
объектов - каждый из которых имеет свой собственный «кешированный»,BearerToken
который он раздает до истечения срока его действия.hasExpired()
Метод наBeraerToken
называются в фабрикахget()
метод , чтобы гарантировать ее не раздает терянного маркер. Если истек срок действия, новый сервер будет запрашивать новый токен. Абзац, следующий за блоком кода, объясняет это более подробно.Вот как реализовать простое
singleton
:Вот как правильно ленивым создать свой
singleton
:источник
getInstance()
. Но на самом деле, если у вас нет других статических методов в вашем классе,Singleton
и вы только вызываете,getInstance()
реальной разницы нет.Вам нужно дважды проверить идиому, если вам нужно лениво загрузить переменную экземпляра класса. Если вам нужно лениво загрузить статическую переменную или синглтон, вам нужна инициализация по требованию .
Кроме того, если синглтон должен быть seriliazble, все другие поля должны быть переходными, и метод readResolve () должен быть реализован для поддержания инварианта синглтон-объекта. В противном случае, каждый раз, когда объект десериализуется, будет создан новый экземпляр объекта. Функция readResolve () заменяет новый объект, считываемый readObject (), что вынуждает этот новый объект собирать мусор, поскольку на него нет ссылающейся переменной.
источник
Различные способы сделать одноэлементный объект:
Согласно Джошуа Блоху - Enum будет лучшим.
Вы также можете использовать двойную проверку блокировки.
Можно использовать даже внутренний статический класс.
источник
Enum singleton
Самый простой способ реализовать Singleton, который является потокобезопасным, - это использовать Enum
Этот код работает с момента появления Enum в Java 1.5
Двойная проверка блокировки
Если вы хотите закодировать «классический» синглтон, который работает в многопоточной среде (начиная с Java 1.5), вы должны использовать это.
Это не поточно-ориентированный до 1.5, потому что реализация ключевого слова volatile была другой.
Ранняя загрузка Singleton (работает еще до Java 1.5)
Эта реализация создает экземпляр singleton при загрузке класса и обеспечивает безопасность потоков.
источник
Для JSE 5.0 и выше используйте подход Enum, в противном случае используйте подход статического одиночного держателя ((подход с отложенной загрузкой, описанный Биллом Пью). Последнее решение также является поточно-ориентированным, не требуя специальных языковых конструкций (то есть volatile или синхронизированных).
источник
Другим аргументом, часто используемым против синглетонов, являются их проблемы с тестируемостью. Синглтоны нелегко поддаются издевательству в целях тестирования. Если это окажется проблемой, я хотел бы сделать следующее небольшое изменение:
Добавленный
setInstance
метод позволяет установить макет реализации синглтон-класса во время тестирования:Это также работает с подходами ранней инициализации:
Это имеет недостаток предоставления этой функциональности и обычному приложению. Другие разработчики, работающие над этим кодом, могут испытывать соблазн использовать метод «setInstance» для изменения определенной функции и, таким образом, изменения поведения всего приложения, поэтому этот метод должен содержать хотя бы хорошее предупреждение в своем javadoc.
Тем не менее, для возможности макет-тестирования (когда это необходимо), такой доступ к коду может быть приемлемой ценой.
источник
Простейший синглтон-класс
источник
Я до сих пор думаю, что после Java 1.5, enum - лучшая из доступных одноэлементных реализаций, поскольку она также гарантирует, что даже в многопоточных средах - создается только один экземпляр.
public enum Singleton{ INSTANCE; }
и все готово !!!
источник
Посмотрите на этот пост.
Примеры шаблонов проектирования GoF в основных библиотеках Java
Из раздела «Синглтон» лучшего ответа,
Вы также можете изучить пример Singleton из самих нативных классов Java.
источник
Лучший шаблон синглтона, который я когда-либо видел, использует интерфейс поставщика.
См. ниже:
источник
Иногда простого «
static Foo foo = new Foo();
» недостаточно. Просто подумайте о какой-то базовой вставке данных, которую вы хотите сделать.С другой стороны, вам придется синхронизировать любой метод, который создает экземпляр одноэлементной переменной как таковой. Синхронизация не так уж плоха, но она может привести к проблемам с производительностью или блокировкам (в очень очень редких ситуациях, использующих этот пример.
Что теперь происходит? Класс загружается через загрузчик классов. Непосредственно после того, как класс был интерпретирован из байтового массива, виртуальная машина выполняет статический {} -блок. в этом весь секрет: статический блок вызывается только один раз, когда данный класс (имя) данного пакета загружается этим загрузчиком классов.
источник
Поскольку мы добавили ключевое слово Synchronized перед getInstance, мы избежали условия гонки в случае, когда два потока вызывают getInstance одновременно.
источник