Каков эффективный способ реализации одноэлементного шаблона в Java?
Каков эффективный способ реализации одноэлементного шаблона в Java?
Ответы:
Используйте перечисление:
public enum Foo {
INSTANCE;
}
Джошуа Блох объяснил этот подход в своем выступлении « Эффективная Java Reloaded» на Google I / O 2008: ссылка на видео . Также см. Слайды 30-32 его презентации ( ffective_java_reloaded.pdf ):
Правильный способ реализации сериализуемого синглтона
public enum Elvis { INSTANCE; private final String[] favoriteSongs = { "Hound Dog", "Heartbreak Hotel" }; public void printFavorites() { System.out.println(Arrays.toString(favoriteSongs)); } }
Edit: онлайн часть «Эффективное Java» , говорит:
«Этот подход функционально эквивалентен подходу открытого поля, за исключением того, что он более лаконичен, предоставляет механизм сериализации бесплатно и обеспечивает железную гарантию от множественных реализаций, даже перед сложными атаками сериализации или рефлексии. Хотя этот подход имеет Все еще широко распространенный, одноэлементный тип перечисления является лучшим способом реализации синглтона ».
В зависимости от использования, есть несколько «правильных» ответов.
Начиная с java5, лучший способ сделать это - использовать enum:
public enum Foo {
INSTANCE;
}
До java5, самый простой случай:
public final class Foo {
private static final Foo INSTANCE = new Foo();
private Foo() {
if (INSTANCE != null) {
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
return INSTANCE;
}
public Object clone() throws CloneNotSupportedException{
throw new CloneNotSupportedException("Cannot clone instance of this class");
}
}
Давайте пройдемся по коду. Во-первых, вы хотите, чтобы урок был окончательным. В этом случае я использовал final
ключевое слово, чтобы пользователи знали, что оно является окончательным. Затем вам нужно сделать конструктор закрытым, чтобы пользователи не могли создавать свои собственные Foo. Создание исключения из конструктора не позволяет пользователям использовать отражение для создания второго Foo. Затем вы создаете private static final Foo
поле для хранения единственного экземпляра и public static Foo getInstance()
метод для его возврата. Спецификация Java гарантирует, что конструктор вызывается только при первом использовании класса.
Если у вас очень большой объект или код тяжелой конструкции И также есть другие доступные статические методы или поля, которые могут быть использованы до того, как понадобится экземпляр, тогда и только тогда вам нужно использовать отложенную инициализацию.
Вы можете использовать private static class
для загрузки экземпляра. Код будет выглядеть так:
public final class Foo {
private static class FooLoader {
private static final Foo INSTANCE = new Foo();
}
private Foo() {
if (FooLoader.INSTANCE != null) {
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
return FooLoader.INSTANCE;
}
}
Поскольку строка private static final Foo INSTANCE = new Foo();
выполняется только тогда, когда класс FooLoader фактически используется, это заботится о отложенной реализации и гарантирует, что она будет поточно-ориентированной.
Если вы также хотите иметь возможность сериализации вашего объекта, вам нужно убедиться, что десериализация не создаст копию.
public final class Foo implements Serializable {
private static final long serialVersionUID = 1L;
private static class FooLoader {
private static final Foo INSTANCE = new Foo();
}
private Foo() {
if (FooLoader.INSTANCE != null) {
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
return FooLoader.INSTANCE;
}
@SuppressWarnings("unused")
private Foo readResolve() {
return FooLoader.INSTANCE;
}
}
Метод readResolve()
гарантирует, что единственный экземпляр будет возвращен, даже если объект был сериализован в предыдущем запуске вашей программы.
Отказ от ответственности: я только что суммировал все удивительные ответы и написал это в моих словах.
При реализации Singleton у нас есть 2 варианта
1. Ленивая загрузка
2. Ранняя загрузка
Ленивая загрузка добавляет немного накладных расходов (много, если честно), поэтому используйте его только тогда, когда у вас есть очень большой объект или код тяжелой конструкции, а также есть другие доступные статические методы или поля, которые могут быть использованы до того, как потребуется экземпляр, тогда и только тогда вам нужно использовать ленивую инициализацию. В противном случае выбор ранней загрузки - это хороший выбор.
Самый простой способ реализации Singleton это
public class Foo {
// It will be our sole hero
private static final Foo INSTANCE = new Foo();
private Foo() {
if (INSTANCE != null) {
// SHOUT
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
return INSTANCE;
}
}
Все хорошо, кроме его рано загруженного синглтона. Давайте попробуем ленивый загруженный синглтон
class Foo {
// Our now_null_but_going_to_be sole hero
private static Foo INSTANCE = null;
private Foo() {
if (INSTANCE != null) {
// SHOUT
throw new IllegalStateException("Already instantiated");
}
}
public static Foo getInstance() {
// Creating only when required.
if (INSTANCE == null) {
INSTANCE = new Foo();
}
return INSTANCE;
}
}
Пока все хорошо, но наш герой не выживет, сражаясь в одиночку с множеством злых нитей, которые хотят много-много экземпляров нашего героя. Итак, давайте защитим его от зла
class Foo {
private static Foo INSTANCE = null;
// TODO Add private shouting constructor
public static Foo getInstance() {
// No more tension of threads
synchronized (Foo.class) {
if (INSTANCE == null) {
INSTANCE = new Foo();
}
}
return INSTANCE;
}
}
но этого недостаточно, чтобы защитить героя, действительно !!! Это лучшее, что мы можем / должны сделать, чтобы помочь нашему герою
class Foo {
// Pay attention to volatile
private static volatile Foo INSTANCE = null;
// TODO Add private shouting constructor
public static Foo getInstance() {
if (INSTANCE == null) { // Check 1
synchronized (Foo.class) {
if (INSTANCE == null) { // Check 2
INSTANCE = new Foo();
}
}
}
return INSTANCE;
}
}
Это называется «идиома блокировки с двойной проверкой». Легко забыть изменчивое утверждение и трудно понять, почему это необходимо.
Для получения дополнительной информации: http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
Теперь мы уверены в злой нити, но как насчет жестокой сериализации? Мы должны убедиться, что даже при десериализации не создается новый объект
class Foo implements Serializable {
private static final long serialVersionUID = 1L;
private static volatile Foo INSTANCE = null;
// Rest of the things are same as above
// No more fear of serialization
@SuppressWarnings("unused")
private Object readResolve() {
return INSTANCE;
}
}
Метод readResolve()
будет гарантировать, что единственный экземпляр будет возвращен, даже если объект был сериализован в предыдущем запуске нашей программы.
Наконец, мы добавили достаточную защиту от потоков и сериализации, но наш код выглядит громоздким и безобразным. Давайте передадим нашему герою
public final class Foo implements Serializable {
private static final long serialVersionUID = 1L;
// Wrapped in a inner static class so that loaded only when required
private static class FooLoader {
// And no more fear of threads
private static final Foo INSTANCE = new Foo();
}
// TODO add private shouting construcor
public static Foo getInstance() {
return FooLoader.INSTANCE;
}
// Damn you serialization
@SuppressWarnings("unused")
private Foo readResolve() {
return FooLoader.INSTANCE;
}
}
Да, это наш тот же самый герой :)
Так как строка private static final Foo INSTANCE = new Foo();
выполняется только тогда, когда класс FooLoader
фактически используется, это заботится о ленивом экземпляре,
и гарантированно ли это потокобезопасность.
И мы зашли так далеко, вот лучший способ достичь всего, что мы сделали, это наилучший способ
public enum Foo {
INSTANCE;
}
Который внутренне будет рассматриваться как
public class Foo {
// It will be our sole hero
private static final Foo INSTANCE = new Foo();
}
Это оно! Больше нет страха перед сериализацией, потоками и уродливым кодом. Также ENUMS синглтон лениво инициализируется .
Этот подход функционально эквивалентен общедоступному полевому подходу, за исключением того, что он более лаконичен, предоставляет механизм сериализации бесплатно и обеспечивает железную гарантию от множественных экземпляров, даже перед сложными атаками сериализации или отражения. Хотя этот подход еще не получил широкого распространения, одноэлементный тип перечисления является лучшим способом реализации синглтона.
Джошуа Блох в "Эффективной Яве"
Теперь вы, возможно, поняли, почему ENUMS считаются лучшим способом реализации Singleton, и спасибо за ваше терпение :)
Обновил его в моем блоге .
serialVersionUID
из 0L
. Третья проблема: нет настройки. Любые специфичные для класса методы writeObject, readObject, readObjectNoData, writeReplace и readResolve, определенные типами enum, игнорируются во время сериализации и десериализации.
Решение, опубликованное Стю Томпсоном, действительно в Java5.0 и более поздних версиях. Но я бы предпочел не использовать его, потому что я думаю, что он подвержен ошибкам.
Легко забыть изменчивое утверждение и трудно понять, почему это необходимо. Без volatile этот код больше не был бы безопасен для потоков благодаря двойной проверке антипаттерна блокировки. Подробнее об этом см. В параграфе 16.2.4 Java-параллелизма на практике . Вкратце: этот шаблон (до Java5.0 или без оператора volatile) может возвращать ссылку на объект Bar, который (все еще) находится в неправильном состоянии.
Этот шаблон был изобретен для оптимизации производительности. Но это больше не является реальной проблемой. Следующий ленивый код инициализации является быстрым и, что более важно, легче читаемым.
class Bar {
private static class BarHolder {
public static Bar bar = new Bar();
}
public static Bar getBar() {
return BarHolder.bar;
}
}
getBar()
. (И если getBar
он называется «слишком рано», то вы столкнетесь с одной и той же проблемой, независимо от того, как реализованы синглоны.) Вы можете увидеть ленивую загрузку классов приведенного выше кода здесь: pastebin.com/iq2eayiR
Потокобезопасен в Java 5+:
class Foo {
private static volatile Bar bar = null;
public static Bar getBar() {
if (bar == null) {
synchronized(Foo.class) {
if (bar == null)
bar = new Bar();
}
}
return bar;
}
}
РЕДАКТИРОВАТЬ : обратите внимание на volatile
модификатор здесь. :) Это важно, потому что без него JMM (модель памяти Java) не гарантирует другие потоки, чтобы увидеть изменения в его значении. Синхронизация не заботится об этом - она только сериализует доступ к этому блоку кода.
РЕДАКТИРОВАТЬ 2 : Ответ @Bno подробно описывает подход, рекомендованный Биллом Пью (FindBugs), и, возможно, лучше. Читайте и голосуйте за его ответ тоже.
Забудьте о отложенной инициализации , это слишком проблематично. Это самое простое решение:
public class A {
private static final A INSTANCE = new A();
private A() {}
public static A getInstance() {
return INSTANCE;
}
}
Убедитесь, что вам это действительно нужно. Сделайте гугл для "единственного анти-образца", чтобы видеть некоторые аргументы против этого. Полагаю, в этом нет ничего плохого, но это всего лишь механизм раскрытия некоторых глобальных ресурсов / данных, поэтому убедитесь, что это лучший способ. В частности, я нашел внедрение зависимостей более полезным, особенно если вы также используете модульные тесты, потому что 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
/**
* Singleton pattern example using Java Enumj
*/
public enum EasySingleton{
INSTANCE;
}
2) Дважды проверил Блокировка / Ленивая загрузка
/**
* Singleton pattern example with Double checked Locking
*/
public class DoubleCheckedLockingSingleton{
private static volatile DoubleCheckedLockingSingleton INSTANCE;
private DoubleCheckedLockingSingleton(){}
public static DoubleCheckedLockingSingleton getInstance(){
if(INSTANCE == null){
synchronized(DoubleCheckedLockingSingleton.class){
//double checking Singleton instance
if(INSTANCE == null){
INSTANCE = new DoubleCheckedLockingSingleton();
}
}
}
return INSTANCE;
}
}
3) Статический фабричный метод
/**
* Singleton pattern example with static factory method
*/
public class Singleton{
//initailzed during class loading
private static final Singleton INSTANCE = new Singleton();
//to prevent creating another instance of Singleton
private Singleton(){}
public static Singleton getSingleton(){
return INSTANCE;
}
}
Я использую Spring Framework для управления своими синглетонами. Он не обеспечивает «одноэлементность» класса (что на самом деле невозможно, если задействовано несколько загрузчиков классов), но обеспечивает действительно простой способ создания и настройки различных фабрик для создания объектов разных типов.
Версия 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;
}
}
Ленивая загрузка, потокобезопасный с неблокирующим, высокая производительность.
Если вам не нужна ленивая загрузка, просто попробуйте
public class Singleton {
private final static Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() { return Singleton.INSTANCE; }
protected Object clone() {
throw new CloneNotSupportedException();
}
}
Если вы хотите ленивую загрузку и хотите, чтобы ваш Singleton был безопасен для потоков, попробуйте шаблон двойной проверки
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if(null == instance) {
synchronized(Singleton.class) {
if(null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
protected Object clone() {
throw new CloneNotSupportedException();
}
}
Поскольку шаблон двойной проверки не гарантированно сработает (из-за некоторых проблем с компиляторами, я ничего не знаю об этом.), Вы также можете попробовать синхронизировать весь метод getInstance или создать реестр для всех ваших синглетонов.
volatile
Я бы сказал, Enum Singleton
Синглтон, использующий enum в Java, - это, как правило, способ объявить синглтон enum. Enum singleton может содержать переменную экземпляра и метод экземпляра. Для простоты также обратите внимание, что если вы используете какой-либо метод экземпляра, вам нужно обеспечить безопасность потоков этого метода, если это вообще повлияет на состояние объекта.
Использование enum очень просто реализовать и не имеет недостатков в отношении сериализуемых объектов, которые необходимо обойти другими способами.
/**
* Singleton pattern example using Java Enum
*/
public enum Singleton {
INSTANCE;
public void execute (String arg) {
//perform operation here
}
}
Вы можете получить к нему доступ Singleton.INSTANCE
гораздо проще, чем вызывать getInstance()
метод в Singleton.
1.12 Сериализация констант enum
Константы перечисления сериализуются иначе, чем обычные сериализуемые или экстернализуемые объекты. Сериализованная форма константы перечисления состоит исключительно из ее имени; Значения поля константы отсутствуют в форме. Для сериализации константы enum
ObjectOutputStream
записывает значение, возвращаемое методом name константы enum. Для десериализации константы enumObjectInputStream
считывает имя константы из потока; десериализованная константа затем получается путем вызоваjava.lang.Enum.valueOf
метода, передавая тип перечисления константы вместе с полученным именем константы в качестве аргументов. Как и другие сериализуемые или экстернализуемые объекты, константы перечисления могут функционировать как цели обратных ссылок, появляющихся впоследствии в потоке сериализации.Процесс , с помощью которого сериализовать перечисления константы не может быть настроен: любой класс конкретным
writeObject
,readObject
,readObjectNoData
,writeReplace
, иreadResolve
методы , определенные типами перечислений игнорируются во время сериализации и десериализации. Аналогично, любыеserialPersistentFields
илиserialVersionUID
полевые декларации также игнорируются - все типы перечислений имеют фиксированнуюserialVersionUID
из0L
. Документирование сериализуемых полей и данных для перечислимых типов не требуется, так как нет никаких изменений в типе отправляемых данных.
Другая проблема с обычными синглетонами заключается в том, что после реализации Serializable
интерфейса они больше не остаются синглтонами, потому что readObject()
метод всегда возвращает новый экземпляр, такой как конструктор, в Java. Этого можно избежать, используя readResolve()
и удаляя вновь созданный экземпляр, заменив его на синглтон, как показано ниже
// readResolve to prevent another instance of Singleton
private Object readResolve(){
return INSTANCE;
}
Это может стать еще более сложным, если ваш класс Singleton поддерживает состояние, так как вам нужно сделать его временным, но в Enum Singleton сериализация гарантируется JVM.
Хорошо читать
There are 4 ways to create a singleton in java.
1- eager initialization singleton
public class Test{
private static final Test test = new Test();
private Test(){}
public static Test getTest(){
return test;
}
}
2- lazy initialization singleton (thread safe)
public class Test {
private static volatile Test test;
private Test(){}
public static Test getTest() {
if(test == null) {
synchronized(Test.class) {
if(test == null){test = new Test();
}
}
}
return test;
}
3- Bill Pugh Singleton with Holder Pattern (Preferably the best one)
public class Test {
private Test(){}
private static class TestHolder{
private static final Test test = new Test();
}
public static Test getInstance(){
return TestHolder.test;
}
}
4- enum singleton
public enum MySingleton {
INSTANCE;
private MySingleton() {
System.out.println("Here");
}
}
Может быть, немного опоздали с игрой по этому вопросу, но в реализации синглтона есть много нюансов. Держатель рисунка нельзя использовать во многих ситуациях. И IMO при использовании volatile - вы также должны использовать локальную переменную. Давайте начнем с самого начала и повторим проблему. Вы поймете, что я имею в виду.
Первая попытка может выглядеть примерно так:
public class MySingleton {
private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new MySingleton();
}
return INSTANCE;
}
...
}
Здесь у нас есть класс MySingleton, который имеет закрытый статический член с именем INSTANCE и открытый статический метод с именем getInstance (). При первом вызове getInstance () член INSTANCE имеет значение null. Затем поток перейдет в состояние создания и создаст новый экземпляр класса MySingleton. Последующие вызовы getInstance () обнаружат, что переменная INSTANCE уже установлена, и, следовательно, не создадут другой экземпляр MySingleton. Это гарантирует, что существует только один экземпляр MySingleton, который используется всеми вызывающими getInstance ().
Но у этой реализации есть проблема. Многопоточные приложения будут иметь условие гонки при создании единственного экземпляра. Если несколько потоков выполнения попадут в метод getInstance () одновременно (или около того), каждый из них увидит элемент INSTANCE как нулевой. Это приведет к тому, что каждый поток создаст новый экземпляр MySingleton, а затем установит элемент INSTANCE.
private static MySingleton INSTANCE;
public static synchronized MySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new MySingleton();
}
return INSTANCE;
}
Здесь мы использовали ключевое слово synchronized в сигнатуре метода для синхронизации метода getInstance (). Это, безусловно, исправит наше состояние гонки. Теперь потоки будут блокироваться и вводить метод по одному. Но это также создает проблему производительности. Эта реализация не только синхронизирует создание отдельного экземпляра, но и все вызовы getInstance (), включая чтение. Чтения не должны быть синхронизированы, поскольку они просто возвращают значение INSTANCE. Поскольку чтение будет составлять основную часть наших вызовов (помните, что создание экземпляров происходит только при первом вызове), мы подвергнемся ненужному снижению производительности при синхронизации всего метода.
private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronize(MySingleton.class) {
INSTANCE = new MySingleton();
}
}
return INSTANCE;
}
Здесь мы переместили синхронизацию из сигнатуры метода в синхронизированный блок, который оборачивает создание экземпляра MySingleton. Но решает ли это нашу проблему? Что ж, мы больше не блокируем чтения, но мы также сделали шаг назад. Несколько потоков попадут в метод getInstance () в одно и то же время или около того, и все они увидят элемент INSTANCE как нулевой. Затем они попадут в синхронизированный блок, где можно получить блокировку и создать экземпляр. Когда этот поток выходит из блока, другие потоки будут бороться за блокировку, и один за другим каждый поток будет проходить через блок и создавать новый экземпляр нашего класса. Итак, мы вернулись туда, откуда начали.
private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronized(MySingleton.class) {
if (INSTANCE == null) {
INSTANCE = createInstance();
}
}
}
return INSTANCE;
}
Здесь мы выдаем еще одну проверку изнутри блока. Если элемент INSTANCE уже установлен, мы пропустим инициализацию. Это называется двойной проверкой блокировки.
Это решает нашу проблему множественной реализации. Но еще раз, наше решение поставило еще одну проблему. Другие темы могут не «видеть», что участник INSTANCE обновлен. Это из-за того, как Java оптимизирует операции с памятью. Потоки копируют исходные значения переменных из основной памяти в кэш процессора. Изменения значений затем записываются в этот кэш и считываются из него. Это особенность Java, предназначенная для оптимизации производительности. Но это создает проблему для нашей одноэлементной реализации. Второй поток, обрабатываемый другим процессором или ядром с использованием другого кэша, не увидит изменений, внесенных первым. Это приведет к тому, что второй поток увидит член INSTANCE как ноль, что приведет к созданию нового экземпляра нашего синглтона.
private static volatile MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronized(MySingleton.class) {
if (INSTANCE == null) {
INSTANCE = createInstance();
}
}
}
return INSTANCE;
}
Мы решаем это, используя ключевое слово volatile в объявлении члена INSTANCE. Это скажет компилятору всегда читать и записывать в основную память, а не в кэш процессора.
Но это простое изменение имеет свою цену. Поскольку мы обходим кэш-память ЦП, мы будем снижать производительность каждый раз, когда работаем с изменчивым членом INSTANCE, что мы делаем 4 раза. Мы дважды проверяем существование (1 и 2), устанавливаем значение (3), а затем возвращаем значение (4). Можно утверждать, что этот путь является крайним случаем, поскольку мы создаем экземпляр только во время первого вызова метода. Возможно, удар по производительности на создание терпимо. Но даже наш основной вариант использования, читает, будет работать на volatile член дважды. Один раз проверить существование, и снова вернуть его значение.
private static volatile MySingleton INSTANCE;
public static MySingleton getInstance() {
MySingleton result = INSTANCE;
if (result == null) {
synchronized(MySingleton.class) {
result = INSTANCE;
if (result == null) {
INSTANCE = result = createInstance();
}
}
}
return result;
}
Поскольку снижение производительности связано с работой непосредственно на volatile-члене, давайте установим локальную переменную в значение volatile и вместо этого будем работать с локальной переменной. Это уменьшит количество раз, когда мы работаем с энергозависимым, тем самым восстанавливая некоторые из наших потерянных показателей. Обратите внимание, что мы должны снова установить нашу локальную переменную при входе в синхронизированный блок. Это гарантирует, что он в курсе любых изменений, которые произошли, пока мы ожидали блокировки.
Я недавно написал статью об этом. Деконструкция Синглтона . Вы можете найти больше информации об этих примерах и пример паттерна "holder". Существует также реальный пример, демонстрирующий изменчивый подход с двойной проверкой. Надеюсь это поможет.
class MySingleton
- может быть, так и должно быть final
?
BearerToken
Экземпляр не является статичным, потому что он является частью BearerTokenFactory
- который настроен с определенным сервером авторизации. Может быть много BearerTokenFactory
объектов - каждый из которых имеет свой собственный «кешированный», BearerToken
который он раздает до истечения срока его действия. hasExpired()
Метод на BeraerToken
называются в фабриках get()
метод , чтобы гарантировать ее не раздает терянного маркер. Если истек срок действия, новый сервер будет запрашивать новый токен. Абзац, следующий за блоком кода, объясняет это более подробно.
Вот как реализовать простое singleton
:
public class Singleton {
// It must be static and final to prevent later modification
private static final Singleton INSTANCE = new Singleton();
/** The constructor must be private to prevent external instantiation */
private Singleton(){}
/** The public static method allowing to get the instance */
public static Singleton getInstance() {
return INSTANCE;
}
}
Вот как правильно ленивым создать свой singleton
:
public class Singleton {
// The constructor must be private to prevent external instantiation
private Singleton(){}
/** The public static method allowing to get the instance */
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
/**
* The static inner class responsible for creating your instance only on demand,
* because the static fields of a class are only initialized when the class
* is explicitly called and a class initialization is synchronized such that only
* one thread can perform it, this rule is also applicable to inner static class
* So here INSTANCE will be created only when SingletonHolder.INSTANCE
* will be called
*/
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
}
getInstance()
. Но на самом деле, если у вас нет других статических методов в вашем классе, Singleton
и вы только вызываете, getInstance()
реальной разницы нет.
Вам нужно дважды проверить идиому, если вам нужно лениво загрузить переменную экземпляра класса. Если вам нужно лениво загрузить статическую переменную или синглтон, вам нужна инициализация по требованию .
Кроме того, если синглтон должен быть seriliazble, все другие поля должны быть переходными, и метод readResolve () должен быть реализован для поддержания инварианта синглтон-объекта. В противном случае, каждый раз, когда объект десериализуется, будет создан новый экземпляр объекта. Функция readResolve () заменяет новый объект, считываемый readObject (), что вынуждает этот новый объект собирать мусор, поскольку на него нет ссылающейся переменной.
public static final INSTANCE == ....
private Object readResolve() {
return INSTANCE; // original singleton instance.
}
Различные способы сделать одноэлементный объект:
Согласно Джошуа Блоху - Enum будет лучшим.
Вы также можете использовать двойную проверку блокировки.
Можно использовать даже внутренний статический класс.
Enum singleton
Самый простой способ реализовать Singleton, который является потокобезопасным, - это использовать Enum
public enum SingletonEnum {
INSTANCE;
public void doSomething(){
System.out.println("This is a singleton");
}
}
Этот код работает с момента появления Enum в Java 1.5
Двойная проверка блокировки
Если вы хотите закодировать «классический» синглтон, который работает в многопоточной среде (начиная с Java 1.5), вы должны использовать это.
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class){
if (instance == null) {
instance = new Singleton();
}
}
}
return instance ;
}
}
Это не поточно-ориентированный до 1.5, потому что реализация ключевого слова volatile была другой.
Ранняя загрузка Singleton (работает еще до Java 1.5)
Эта реализация создает экземпляр singleton при загрузке класса и обеспечивает безопасность потоков.
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
public void doSomething(){
System.out.println("This is a singleton");
}
}
Для JSE 5.0 и выше используйте подход Enum, в противном случае используйте подход статического одиночного держателя ((подход с отложенной загрузкой, описанный Биллом Пью). Последнее решение также является поточно-ориентированным, не требуя специальных языковых конструкций (то есть volatile или синхронизированных).
Другим аргументом, часто используемым против синглетонов, являются их проблемы с тестируемостью. Синглтоны нелегко поддаются издевательству в целях тестирования. Если это окажется проблемой, я хотел бы сделать следующее небольшое изменение:
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.
Тем не менее, для возможности макет-тестирования (когда это необходимо), такой доступ к коду может быть приемлемой ценой.
Простейший синглтон-класс
public class Singleton {
private static Singleton singleInstance = new Singleton();
private Singleton() {}
public static Singleton getSingleInstance() {
return singleInstance;
}
}
Я до сих пор думаю, что после Java 1.5, enum - лучшая из доступных одноэлементных реализаций, поскольку она также гарантирует, что даже в многопоточных средах - создается только один экземпляр.
public enum Singleton{
INSTANCE;
}
и все готово !!!
Посмотрите на этот пост.
Примеры шаблонов проектирования GoF в основных библиотеках Java
Из раздела «Синглтон» лучшего ответа,
Синглтон (распознаваемый креативными методами, возвращающими один и тот же экземпляр (обычно самого себя) каждый раз)
- java.lang.Runtime # getRuntime ()
- java.awt.Desktop # getDesktop ()
- java.lang.System # getSecurityManager ()
Вы также можете изучить пример Singleton из самих нативных классов Java.
Лучший шаблон синглтона, который я когда-либо видел, использует интерфейс поставщика.
См. ниже:
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();
}
}
Иногда простого « 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;
}
}
Что теперь происходит? Класс загружается через загрузчик классов. Непосредственно после того, как класс был интерпретирован из байтового массива, виртуальная машина выполняет статический {} -блок. в этом весь секрет: статический блок вызывается только один раз, когда данный класс (имя) данного пакета загружается этим загрузчиком классов.
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 одновременно.