Реализация шаблона Singleton из Википедии - PullRequest
6 голосов
/ 10 января 2010

Я имею в виду решение для шаблона Singleton Билла Пью в Википедии:

public class Singleton
{

    // Private constructor prevents instantiation from other classes

    private Singleton() {}

     /**
    * SingletonHolder is loaded on the first execution of Singleton.getInstance() 
    * or the first access to SingletonHolder.INSTANCE, not before.
    */

    private static class SingletonHolder 
    { 

        private static final Singleton INSTANCE = new Singleton();
    }

   public static Singleton getInstance()
   {

       return SingletonHolder.INSTANCE;
   }

}

Здесь они упомянули:

Внутренний класс ссылается не раньше (и поэтому загружается не ранее загрузчиком классов), чем в момент вызова getInstance(). Таким образом, это решение является поточно-ориентированным и не требует специальных языковых конструкций (т. Е. volatile или synchronized).

Однако, не существует ли возможности, что 2 потока будут вызывать getInstance() одновременно, что приведет к созданию двух экземпляров синглтона? Разве это не безопасно использовать synchronized здесь? Если да, где его следует использовать в коде?

Ответы [ 7 ]

12 голосов
/ 10 января 2010

См. «Как это работает» , в статье «Инициализация инициатора по требованию», ссылка на которую есть в том же разделе.

В двух словах, вот что происходит, когда вы звоните getInstance() в первый раз:

  1. JVM видит ссылку на SingletonHolder, на которую он никогда не видел ссылки.
  2. Выполнение приостанавливается, пока оно инициализирует SingletonHolder. Это включает в себя выполнение любой статической инициализации, которая включает экземпляр singleton.
  3. Исполнение возобновляется. Любые другие потоки, вызывающие getInstance() одновременно, увидят, что SingletonHolder уже инициализирован. Спецификация Java гарантирует , что инициализация класса является поточно-ориентированной.
5 голосов
/ 10 января 2010

JLS гарантирует, что JVM не будет инициализировать экземпляр, пока кто-то не вызовет getInstance();, и это будет поточно-ориентированным, поскольку это произойдет во время инициализации класса SingletonHolder.

Однако, , начиная с Java 5, предпочтительный подход включает Enum:

// Enum singleton - the preferred approach
public enum Elvis {
    INSTANCE;

    public void leaveTheBuilding() { ... }
}

Ссылка: Реализация одноэлементного шаблона в Java

3 голосов
/ 10 января 2010

Вот объяснение этого явления из Википедии (выделено мной):

Когда класс Something загружается JVM, класс проходит инициализация. Так как класс делает не имеют статических переменных для инициализация, инициализация завершается тривиально. Статический класс определения LazyHolder внутри него нет инициализируется, пока JVM не определит что LazyHolder должен быть выполнен. только статический класс LazyHolder выполняется, когда статический метод getInstance вызывается в классе Что-то, и первый раз это случается, JVM загрузит и инициализировать класс LazyHolder. инициализация класса LazyHolder приводит к статической переменной что-то инициализируется путем выполнения (частный) конструктор для внешнего Класс Нечто. С классом фаза инициализации гарантируется JLS должен быть последовательным, т.е. не одновременно, не дальше синхронизация требуется в статический метод getInstance во время загрузка и инициализация. и с фаза инициализации записывает статическая переменная что-то в сериале операция, все последующие параллельные вызовы getInstance будут вернуть то же правильно инициализированный что-то без каких-либо несущих дополнительные затраты на синхронизацию.

Итак, в вашем примере Singleton - это «LazyHolder», а SingletonHolder - «Что-то» Повторный вызов getInstance() не вызовет состояние гонки из-за гарантий JLS.

0 голосов
/ 10 января 2010

Эта идиома известна как идиома Инициализация по требованию (IODH) и обсуждается в пункте 48 «Эффективной Java», как напомнил Боб Ли в своем великом посте о Ленивые загрузки синглетонов :

Элемент 48: Синхронизировать доступ к общим изменяемым данным

(...)

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

// The initialize-on-demand holder class idiom
private static class FooHolder {
     static final Foo foo = new Foo();
}
public static Foo getFoo() { return FooHolder.foo; }

Идиома использует в своих интересах гарантировать, что класс не будет инициализируется до его использования [ JLS , 12.4.1]. Когда метод getFoo вызывается впервые, он читает поле FooHolder.foo, вызывающее FooHolder класс для инициализации. Прелесть этой идиомы в том, что getFoo метод не синхронизирован и выполняет только доступ к полю, так ленивая инициализация добавляет практически ничего к стоимости доступа. Единственный недостаток идиомы в том, что это не работает, например, поля, только для статических полей.

Однако во Втором издании Effective Java Джошуа объясняет, как перечисления (ab) могут использоваться для написания сериализуемого синглтона (это должно быть предпочтительным способом в Java 5):

Начиная с версии 1.5, существует третий подход к реализации синглетонов. Просто создать тип enum с одним элементом:

// Enum singleton - the preferred approach
public enum Elvis {
     INSTANCE;
     public void leaveTheBuilding() { ... }
}

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

0 голосов
/ 10 января 2010

Здесь есть два отличия, которые необходимо отметить. Первый - это шаблон проектирования Singleton, а другой - экземпляр Singleton.

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

Версия enum определенно лучше, чем шаблон Singleton.

Если вы не хотите реализовывать все экземпляры как enum экземпляры, то вам следует подумать об использовании Dependency Injection (таких фреймворков, как Google Guice ) для управления одноэлементными экземплярами приложения.

0 голосов
/ 10 января 2010

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

0 голосов
/ 10 января 2010

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

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