среда, 1 сентября 2010 г.

Singleton in .NET 4 - вопрос снят?

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

С моей точки зрения этот вопрос не так уж много показывает (скажем так, он кажется обманчиво интересным), но ответы на сопутствующие вопросы (более интересные) сами его провоцируют. Например, на вопрос "Какие вы знаете шаблоны проектирования", ответ обычно начинается с "Singleton, Factory..."

К сожалению, несмотря на то, что GoF имеет успех уже на протяжении более 15 лет, достаточно часто ответ на этом заканчивается. Кстати, на второй часто задаваемый вопрос "Чем отличается Factory от Abstract Factory" ответ дается еще реже.

Но давайте разберемся, стоит ли начинать ответ с Singleton? Наверное, не стоит. Вообще, многие считают, что Singleton - это anti-pattern. И в любом случае, Singleton - это далеко не самый интересный шаблон проектирования.

Что также хочется отметить, обычно просят реализовать просто Singleton, не упоминая ленивую инициализацию, однако почему-то на доске очень часто появляется double checked locking. Это плохой стереотип - все так делают, значит, так нужно. В реальной жизни ленивая инициализация чаще всего не требуется, а еще чаще стоимость многопоточной реализации Singleton (потому что у дополнительной синхронизации есть стоимость) превышает экономию на отложенной инициализации.

Так вот, к чему это я все? :) Проблема настолько избита, что в .NET 4.0 ввели специальный класс, который наконец-то дает нам out-of-the-box реализацию, - Lazy, и реализация значительно упрощается:

   1: public class LazySingleton
   2: {
   3:     // static holder for instance, need to use lambda to construct since constructor private
   4:     private static readonly Lazy<LazySingleton> _instance
   5:         = new Lazy<LazySingleton>(() => new LazySingleton());
   6:  
   7:     // private to prevent direct instantiation.
   8:     private LazySingleton()
   9:     {
  10:     }
  11:  
  12:     // accessor for instance
  13:     public static LazySingleton Instance
  14:     {
  15:         get
  16:         {
  17:             return _instance.Value;
  18:         }
  19:     }
  20: }

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

Чуть побольше про ленивую инициализацию и Lazy можно почитать на MSDN здесь. Есть и на русском, но поверьте, оно вам на таком русском не нужно.

И вот казалось бы, одним вопросом для дотнетчиков меньше. Но что-то у меня есть подозрения, что вопрос еще какое-то время побудет актуальным :) Как считаете?

13 комментариев:

  1. 1го сентября - день знаний на shcoder.by :)

    ОтветитьУдалить
  2. Это мода, сейчас все говорят это это анти-паттерн и не знают почему, и т.д. Глупость всё это, всегда что-то будет первым и всегда будут какие-то заученные модные ответа не имеющие под собой знаний. Главное не какой-то конкретный паттерн знать, а иметь голову на плечах. А все шаблонные темы на собеседованиях просто для того чтобы был объект разговора.

    ОтветитьУдалить
  3. Конечно, главное иметь голову на плечах. Я вопрос про Singleton задаю, если есть информационный повод, а так - есть и поинтереснее темы.

    А GoF паттерны нужно знать. В конце концов, если есть голова на плечах, то почему до сих пор не добрался до паттернов

    ОтветитьУдалить
  4. Иметь голову на плечах это хорошо, а вот иметь голову на плечах, в которой есть понимание зачем нужны шаблоны, как они могут помочь и помогают, что характерно, в разработке и проектировании как архитектору так и программеру. Вот это и можно выяснить на собеседовании с кандидатом, и шаблоны как нельзя лучше подходят для таких целей имхо.Кстати вопрос о Singleton http://msdn.microsoft.com/en-us/library/ff650316.aspx давным давно решен %)

    ОтветитьУдалить
  5. А чем плоха реализация Singleton на с# через статик конструктор?

    ОтветитьУдалить
  6. Анонимусу

    Про реализацию через статический конструктор чуть-чуть здесь написано - http://www.yoda.arachsys.com/csharp/singleton.html. В общем, не совсем ленивый он, есть сложности.

    ОтветитьУдалить
  7. To Дмитрий Гордиенко

    Вопрос-то решен, но к сожалению не все об этом в курсе. Lazy просто сокращает количество необходимого кода. И, как я уже писал, что-то мне кажется, что даже не смотря на наличие Lazy все равно будут приходить и писать что-то непонятное.

    ОтветитьУдалить
  8. Если вы спрашиваете про синглетон на собеседованиях и припоминаете многопоточность, то примите ли ответ с двойной проверкой?

    ОтветитьУдалить
  9. Как было указано выше, двойная блокировка вполне себе работает, просто нужно не забыть про volatile.

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

    Да и на практике вообще достаточно знать, что все не так просто, и google под рукой.

    ОтветитьУдалить
  10. >Как было указано выше, двойная блокировка вполне себе работает

    Всё-таки не "двойная блокировка" (блокировка-то одна), а "двойная проверка" (до блокировки и после) ;о) не оплошайте на собеседовании :о)

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

    Да и "немногопоточного" синглетона не бывает: общепринятой практикой является делать thread-safe доступ к статическим данным.

    Так же по поводу ленивости - вы не показали ещё один пример с вложенным статическим классом, который позволяет обходиться без Lazy уже подчти десяток лет ;о)

    ОтветитьУдалить
  11. Спсб, двойная проверка, да :) Double-checked locking может не работать без volatile, а так же в лохматых версиях джавы и может быть .net.

    Я не показывал все возможные варианты - их можно найти в интернете - хотелось просто показать про общие проблемы и про новый класс Lazy.

    ОтветитьУдалить
  12. Сабж для .NET от MS никогда проблемой не был (в Mono возможно будет по другому). В CLR есть средства для этого - гуглим по ключевому слову "beforefieldinit"

    ОтветитьУдалить
  13. Xna, решений этой задачи много, проблема в том, что очень многие их не знают.

    ОтветитьУдалить