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

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

30 ответов


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

public enum Foo {
    INSTANCE;
}

Джошуа блох объяснил этот подход в его Эффективная Перезагрузка Java обсуждение в Google I / O 2008:ссылка на видео. Также смотрите слайды 30-32 его презентации (effective_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: An онлайн-часть "эффективной Java" говорит:

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


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

так как java5 лучший способ сделать это-использовать перечисление:

public enum Foo {
   INSTANCE;
}

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

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

можно использовать 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;
    }
}

все хорошо, кроме его рано загруженного синглтона. Давайте попробуем lazy loaded singleton

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

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

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

наконец, мы добавили достаточную защиту от потоков и сериализации, но наш код выглядит громоздким и уродливым. Давайте дадим нашему герою make over

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

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

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

-Джошуа блох в "эффективная Java"

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


решение, опубликованное Stu Thompson, действительно в 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;
    }
}

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

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

Изменить 2: @BNO ' s ответ подробно подход, рекомендованный Биллом пью (FindBugs) и спорно лучше. Иди, почитай и проголосуй за его ответ.


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

public class A {    

    private static final A INSTANCE = new A();

    private A() {}

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

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


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


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

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

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

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


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

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

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


Ниже приведены 3 различных подхода

1) перечисление

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

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


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


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

public class Singleton {
    private final static Singleton INSTANCE = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() { return Singleton.INSTANCE; }

    protected Object clone() {
        throw new CloneNotSupportedException();
    }
}

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

public class Singleton {
        private static Singleton instance = null;

        private Singleton() {}

        public static Singleton getInstance() { 
              if(null == instance) {
                  synchronized(Singleton.class) {
                      if(null == instance) {
                          instance = new Singleton();
                      }
                  }
               }
               return instance;
        }

        protected Object clone() {
            throw new CloneNotSupportedException();
        }
}

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


Я бы сказал Enum singleton

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

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

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

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

1.12 сериализация констант перечисления

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

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

цитата из Oracle docs

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

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

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


Читать

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

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, который имеет частный статический член вызывается экземпляр и открытый статический метод getInstance (). При первом вызове getInstance() член экземпляра имеет значение null. Затем поток попадет в условие создания и создаст новый экземпляр класса MySingleton. Последующие вызовы getInstance () обнаружат, что переменная экземпляра уже установлена и поэтому не создает другой экземпляр MySingleton. Это гарантирует, что существует только один экземпляр MySingleton, который совместно используется всеми вызывающими абонентами getInstance ().

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


private static MySingleton INSTANCE;

public static synchronized MySingleton getInstance() {
    if (INSTANCE == null) {
        INSTANCE = new MySingleton();
    }

    return INSTANCE;
}

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


private static MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronize(MySingleton.class) {
            INSTANCE = new MySingleton();
        }
    }

    return INSTANCE;
}

здесь мы переместили синхронизацию из сигнатуры метода в синхронизированный блок, который обертывает создание экземпляра MySingleton. Но решает ли это нашу проблему? Ну, мы больше не блокируем чтение, но мы также сделали шаг назад. Несколько потоков попадут в метод getInstance () примерно в одно и то же время и все они будут видеть член экземпляра как null. Затем они попадут в синхронизированный блок, где один получит блокировку и создаст экземпляр. Когда этот поток выйдет из блока, другие потоки будут бороться за блокировку, и один за другим каждый поток будет проходить через блок и создавать новый экземпляр нашего класса. Итак, мы вернулись к тому, с чего начали.


private static MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronized(MySingleton.class) {
            if (INSTANCE == null) {
                INSTANCE = createInstance();
            }
        }
    }

    return INSTANCE;
}

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

это решает нашу проблему несколько экземпляров. Но наше решение вновь поставило перед нами еще одну задачу. Другие потоки могут не "видеть", что элемент экземпляра обновлен. Это связано с тем, как Java оптимизирует операции с памятью. Потоки копируют исходные значения переменных из основной памяти в кэш процессора. Изменения значений затем записываются в этот кэш и считываются из него. Это особенность Java предназначен для оптимизации производительности. Но это создает проблему для нашей реализации синглтона. Второй поток, обрабатываемый другим процессором или ядром, используя другой кэш, не увидит изменений, внесенных первым. Это заставит второй поток видеть член экземпляра как null, заставляющий новый экземпляр нашего синглтона быть созданным.


private static volatile MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronized(MySingleton.class) {
            if (INSTANCE == null) {
                INSTANCE = createInstance();
            }
        }
    }

    return INSTANCE;
}

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

но это простое изменение имеет свою цену. Поскольку мы обходим кэш ЦП, мы будем принимать удар производительности каждый раз, когда мы работаем с изменчивым членом экземпляра - что мы делаем 4 раза. Мы дважды проверяем существование (1 и 2), устанавливаем значение (3), а затем возвращаем значение (4). Можно утверждать, что этот путь является пограничным случаем, поскольку мы создаем экземпляр только во время первого вызова метода. Возможно производительность при создании терпима. Но даже наш основной прецедент, читает, будет работать на летучем члене дважды. Один раз проверить существование, и снова вернуть его значение.


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

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


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

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

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

различные способы сделать объект singleton:

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

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

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


перечисление синглтон

самый простой способ реализовать Одноэлемент, который является потокобезопасным, - это использовать перечисление

public enum SingletonEnum {
  INSTANCE;
  public void doSomething(){
    System.out.println("This is a singleton");
  }
}

этот код работает с момента введения перечисления в 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)

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

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

}

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

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


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

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 метод позволяет установить макет реализации класса singleton во время тестирования:

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 для изменения alter определенной функции и, таким образом, изменения всего поведения приложения, поэтому этот метод должен содержать по крайней мере хорошее предупреждение в 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

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

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

  • java.ленг.Runtime#getRuntime ()
  • java.ОУ.Рабочий стол#getDesktop ()
  • java.ленг.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;
    }

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

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 одновременно.