it-swarm-ru.tech

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

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

771
Riyaz Mohammed Ibrahim

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

public enum Foo {
    INSTANCE;
}

Джошуа Блох объяснил этот подход в своей Эффективной Java перезагрузке речи на 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));
    }
}

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

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

757
Stephen Denne

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

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

227
Roel Spilker

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


При реализации 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, и спасибо за ваше терпение :)
Обновил его в моем блоге .

131
xyz

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

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

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

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

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

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

EDIT: обратите внимание на модификатор volatile здесь. :) Это важно, потому что без него JMM (модель памяти Java) не гарантирует другие потоки, чтобы увидеть изменения в его значении. Синхронизация не делает позаботится об этом - она ​​только сериализует доступ к этому блоку кода.

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

94
Stu Thompson

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

public class A {    

    private static final A INSTANCE = new A();

    private A() {}

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

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

47
Neil Burroughs

Не забывайте, что Singleton - это только Singleton для загрузчика классов, который его загрузил. Если вы используете несколько загрузчиков (контейнеров), каждый из них МОЖЕТ иметь свою собственную версию Singleton.

26
Javamann

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

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

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

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

21
Andrew Swan

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

Лично я стараюсь избегать синглетонов как можно чаще по многим причинам, опять же, большинство из которых можно найти, прибегая к помощи синглетонов. Я чувствую, что нередко злоупотребляют одиночками, потому что их легко понять всем, они используются как механизм для включения "глобальных" данных в дизайн OO, и их используют потому, что их легко обойти управление жизненным циклом объекта (или действительно думать о том, как вы можете сделать А изнутри Б). Посмотрите на такие вещи, как Inversion of Control (IoC) или Inpendency Injection (DI) для Nice среднего уровня.

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

20
Aidos

Ниже приведены 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;
    }
}
16
Abhijit Gaikwad

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

13
Matt

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

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

11
coderz

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

10
macbirdie

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

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

10
Aleksi Yrttiaho

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

8
Michael Andrews

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

Singleton, использующий enum в Java, обычно является способом объявления enum singleton. 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

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

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

цитируется по документации Oracle

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

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

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


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

  1. шаблон синглтона
  2. Перечисления, синглтоны и десериализация
  3. Двойная проверка блокировки и шаблон Singleton
8
NullPoiиteя
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");
    }
}
7
Dheeraj Sachan

Вот как реализовать простое 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();
    }
}
4
Nicolas Filotto

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

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

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

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

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

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

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

3
Shailendra Singh

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

}
3
Dan Moldovan

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

2
raoadnan

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

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.

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

2
user3792852

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

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

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

public enum Singleton{ INSTANCE; }

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

0
shikjohari

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

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

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

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

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

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

0
itiskj

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

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

Увидеть ниже:

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