Дважды проверил схему блокировки: сломан или нет? - PullRequest
9 голосов
/ 01 сентября 2010

Почему шаблон считается сломанным?Это выглядит хорошо для меня?Есть идеи?

public static Singleton getInst() {
    if (instace == null) createInst();
    return instace;
}

private static synchronized createInst() {
     if (instace == null) {
         instace = new Singleton(); 
     }
}

Ответы [ 7 ]

21 голосов
/ 01 сентября 2010

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

  1. Поток A замечает, что значение не инициализировано, поэтому он получает блокировку и начинает инициализировать значение.
  2. Сгенерированный кодкомпилятору разрешено обновлять совместно используемую переменную, чтобы она указала на частично созданный объект, прежде чем А. завершит выполнение инициализации.
  3. Поток B замечает, что совместно используемая переменная была инициализирована (или она так выглядит), и возвращаетего ценность.Поскольку поток B считает, что значение уже инициализировано, он не получает блокировку.Если B использует объект до того, как вся инициализация, выполненная A, будет видна B, программа, скорее всего, потерпит крах.

Этого можно избежать, используя ключевое слово «volatile» для правильной обработки ваших экземпляров-одиночек

11 голосов
/ 01 сентября 2010

Вся дискуссия - это огромная, бесконечная трата мозгового времени.В 99,9% случаев синглтоны не несут каких-либо значительных затрат на установку, и для искусственных установок нет никаких оснований для достижения несинхронизированной гарантированной отложенной загрузки.1003 *

public class Singleton{
    private Singleton instance = new Singleton();
    private Singleton(){ ... }
    public Singleton getInstance(){ return instance; }
}

Еще лучше, сделайте перечисление:

public enum Singleton{
    INSTANCE;
    private Singleton(){ ... }
}
7 голосов
/ 01 сентября 2010

Я не знаю, сломался ли он, но на самом деле это не самое эффективное решение из-за синхронизации, которая довольно дорогая.Лучшим подходом было бы использование идиомы «Инициализация по требованию», которая загружает ваш синглтон в память при первом запросе, как следует из названия, и, следовательно, ленивая загрузка.Самое большое преимущество, которое вы получаете с этой идиомой, заключается в том, что вам не нужно синхронизировать, потому что JLS обеспечивает последовательную загрузку классов.

Подробная запись в википедии по теме: http://en.wikipedia.org/wiki/Initialization_on_demand_holder_idiom

ДругойСледует иметь в виду, что, поскольку появились структуры внедрения зависимостей, такие как Spring и Guice, экземпляры классов создаются и управляются этими контейнерами, и они при желании предоставят вам Singleton, поэтому не стоит ломать голову над этим.это, если вы не хотите научиться мыслить за шаблоном, что полезно.Также обратите внимание, что синглтоны, предоставляемые этими контейнерами IOC, являются синглетами на экземпляр контейнера, но обычно у вас будет один контейнер IOC на приложение, поэтому это не станет проблемой.

6 голосов
/ 01 сентября 2010

Проблема заключается в следующем: ваша JVM может изменить порядок кода, и поля не всегда одинаковы для разных потоков.Посмотрите на это: http://www.ibm.com/developerworks/java/library/j-dcl.html. Использование ключевого слова volatile должно исправить это, но оно не работает до версии Java 1.5.

В большинстве случаев одиночная проверка блокировки выполняется более чем достаточно быстро, попробуйте это:

// single checked locking: working implementation, but slower because it syncs all the time
public static synchronized Singleton getInst() {
    if (instance == null) 
        instance = new Singleton();
    return instance;
}

Также взгляните на эффективную Java, где вы найдете большую главу по этой теме.

Подводя итог: Не сделать двойную проверкублокировка, есть лучшие идомы.

4 голосов
/ 01 сентября 2010

Идиома держателя по требованию , да вот и все:

public final class SingletonBean{

    public static SingletonBean getInstance(){
        return InstanceHolder.INSTANCE;
    }

    private SingletonBean(){}

    private static final class InstanceHolder{
        public static final SingletonBean INSTANCE = new SingletonBean();
    }

}

Хотя Джошуа Блох также рекомендует одноэлементный шаблон Enum в Effective Java Глава 2, Элемент3:

// Enum singleton - the prefered approach
public enum Elvis{
    INSTANCE;
    public void leaveTheBuilding(){ ... }
}
2 голосов
/ 01 сентября 2010

Большинство ответов здесь верны о том, почему он сломан, но неверны или предлагают сомнительные стратегии для решения.

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

private static Singleton instance = createInst();

public static Singleton getInst() {
    return instance ;
}

private static synchronized createInst() {
    return new Singleton(); 
}

Спецификация языка Java гарантирует, что статические инициализаторы будут запускаться только один раз, когда класс загружается впервые, и гарантированным потокобезопасным способом.

2 голосов
/ 01 сентября 2010

Это не отвечает на ваш вопрос (другие уже сделали), но я хочу рассказать вам о своем опыте с инициализированными объектами синглетонов / ленивыми:

У нас была пара синглетонов в нашем коде. Однажды нам пришлось добавить параметр конструктора к одному синглтону, и возникла серьезная проблема, потому что конструктор этого синглтона был вызван в геттере. Были только следующие возможные решения:

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

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

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