Thread Safe C # Singleton Pattern
У меня есть некоторые вопросы относительно шаблона singleton, как описано здесь: http://msdn.microsoft.com/en-us/library/ff650316.aspx
Следующий код является выпиской из статьи:
using System; public sealed class Singleton { private static volatile Singleton instance; private static object syncRoot = new object(); private Singleton() {} public static Singleton Instance { get { if (instance == null) { lock (syncRoot) { if (instance == null) instance = new Singleton(); } } return instance; } } }
В частности, в приведенном выше примере необходимо ли сравнивать экземпляр с null в два раза до и после блокировки? Это необходимо? Почему бы не выполнить блокировку сначала и провести сравнение?
- Как объявить глобальные переменные в Android?
- Когда сборщик мусора стирает экземпляр объекта, который использует шаблон Singleton?
- Использование шаблона проектирования Singleton для SQLiteDatabase
- должно ли соединение db быть одиночным?
- Существуют ли жизнеспособные альтернативы шаблону Singleton в GOF?
Есть ли проблема в упрощении следующего?
public static Singleton Instance { get { lock (syncRoot) { if (instance == null) instance = new Singleton(); } return instance; } }
Является ли выполнение блокировки дорогостоящим?
- Единичное тестирование с одноточечными
- Шаблон для ленивого streamобезопасного создания одноэлементов в java
- Явная блокировка Java
- C ++ singleton против глобального статического объекта
- Реализация одноэлементного шаблона в Java
- Безопасность streamов в Синглтоне
- Что так плохо о одиночных играх?
- Может ли кто-нибудь предоставить мне образец Singleton в c ++?
Выполнение блокировки ужасно дорого по сравнению с простым instance != null
проверки указателя instance != null
.
Образец, который вы видите здесь, называется двойной проверкой блокировки . Его цель состоит в том, чтобы избежать дорогостоящей операции блокировки, которая будет нужна только один раз (когда односторонний доступ к первому доступу). Реализация такова, потому что она также должна гарантировать, что при инициализации синглтона не будет ошибок, вызванных условиями гонки нитей.
Подумайте об этом так: голая null
проверка (без lock
) гарантированно даст вам правильный полезный ответ, только когда этот ответ «да, объект уже построен». Но если ответ «еще не сконструирован», то у вас недостаточно информации, потому что то, что вы действительно хотели знать, это то, что он «еще не сконструирован, и ни один другой stream не собирается строить его в ближайшее время ». Таким образом, вы используете внешнюю проверку в качестве очень быстрого первоначального теста, и вы инициируете правильную, без ошибок, но «дорогостоящую» процедуру (блокировка, а затем проверка), только если ответ «нет».
Вышеприведенная реализация достаточно хороша для большинства случаев, но на данный момент неплохо пойти и прочитать статью Джона Скита о одиночных играх на C #, которая также оценивает другие альтернативы.
Ленивая версия:
public sealed class Singleton { static readonly Lazy lazy = new Lazy (() => new Singleton()); private Singleton() { } public static Singleton Instance => lazy.Value; }
Требуется .NET 4 и C # 6.0 (VS2015) или новее.
Выполнение блокировки: довольно дешево (еще дороже, чем нулевой тест).
Выполнение блокировки, когда другой stream имеет это: вы получаете стоимость того, что им еще нужно делать при блокировке, добавленной в свое время.
Выполнение блокировки, когда другой stream имеет ее, и на нее также ждут десятки других streamов: Crippling.
По соображениям производительности вы всегда хотите иметь блокировки, которые требуется другому streamу, в течение самого короткого периода времени.
Разумеется, легче рассуждать о «широких» замках, чем узких, поэтому стоит начать с них широким и оптимизировать по мере необходимости, но есть некоторые случаи, которые мы узнаем из опыта и знакомства, где более узкий подход к шаблону.
(Кстати, если вы можете просто использовать private static volatile Singleton instance = new Singleton()
или если вы можете просто не использовать одиночные игры, но вместо этого использовать статический class, то оба лучше подходят для этих проблем).
Причина в производительности. Если instance != null
(который всегда будет иметь место, за исключением самого первого раза), нет необходимости выполнять дорогостоящую lock
: два streamа, обращающихся к инициализированному одноэлементному соединению одновременно, будут синхронизироваться неслучайно.
Почти в каждом случае (то есть: все случаи, кроме самых первых), instance
не будет равен нулю. Приобретение блокировки является более дорогостоящим, чем простая проверка, поэтому проверка, когда значение instance
перед блокировкой является хорошей и бесплатной оптимизацией.
Этот шаблон называется блокировкой с двойной проверкой: http://en.wikipedia.org/wiki/Double-checked_locking
Джеффри Рихтер рекомендует следующее:
public sealed class Singleton { private static readonly Object s_lock = new Object(); private static Singleton instance = null; private Singleton() { } public static Singleton Instance { get { if(instance != null) return instance; Monitor.Enter(s_lock); Singleton temp = new Singleton(); Interlocked.Exchange(ref instance, temp); Monitor.Exit(s_lock); return instance; } } }
Вы можете с нетерпением создать streamобезопасный экземпляр Singleton, в зависимости от потребностей вашего приложения, это краткий код, хотя я бы предпочел ленивую версию @ andasa.
public sealed class Singleton { private static readonly Singleton instance = new Singleton(); private Singleton() { } public static Singleton Instance() { return instance; } }