У меня есть несколько вопросов относительно шаблона 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;
}
}
}
В частности, в приведенном выше примере есть ли необходимость сравнивать экземпляр с нулевым значением дважды, до и после блокировки? Это необходимо? Почему бы сначала не выполнить блокировку и не провести сравнение?
Есть ли проблема в упрощении до следующего?
public static Singleton Instance
{
get
{
lock (syncRoot)
{
if (instance == null)
instance = new Singleton();
}
return instance;
}
}
Дорогое ли выполнение блокировки?
c#
design-patterns
singleton
Уэйн Фиппс
источник
источник
Ответы:
Выполнение блокировки ужасно дорого по сравнению с простой проверкой указателя
instance != null
.Шаблон, который вы видите здесь, называется блокировкой с двойной проверкой . Его цель - избежать дорогостоящей операции блокировки, которая понадобится только один раз (при первом обращении к синглтону). Реализация такова, потому что она также должна гарантировать, что при инициализации синглтона не будет ошибок, связанных с условиями гонки потоков.
Подумайте об этом так: простая
null
проверка (без символаlock
) гарантированно даст вам правильный полезный ответ только в том случае, если этот ответ - «да, объект уже построен». Но если ответ «еще не построен», то у вас недостаточно информации, потому что вы действительно хотели знать, что он «еще не построен». и никакой другой поток не собирается его создавать в ближайшее время ». Таким образом, вы используете внешнюю проверку как очень быструю начальную проверку и инициируете правильную, безошибочную, но «дорогостоящую» процедуру (блокировка, затем проверка) только в том случае, если ответ - «нет».Вышеупомянутая реализация достаточно хороша для большинства случаев, но сейчас рекомендуется пойти и прочитать статью Джона Скита о синглтонах в C #, в которой также оцениваются другие альтернативы.
источник
Lazy<T>
, эта работа отлично справляется.Lazy<T>
Версия:public sealed class Singleton { private static readonly Lazy<Singleton> lazy = new Lazy<Singleton>(() => new Singleton()); public static Singleton Instance => lazy.Value; private Singleton() { } }
Требуется .NET 4 и C # 6.0 (VS2015) или новее.
источник
Выполнение блокировки: довольно дешево (все же дороже, чем нулевой тест).
Выполнение блокировки, когда это есть у другого потока: вы получаете стоимость того, что им еще предстоит сделать во время блокировки, добавленную к вашему собственному времени.
Выполнение блокировки, когда она есть у другого потока, и десятки других потоков также ожидают ее: Крушение.
По соображениям производительности вам всегда нужно иметь блокировки, которые нужны другому потоку, на максимально короткий период времени.
Конечно, легче рассуждать о «широких» блокировках, чем об узких, поэтому стоит начать с их широких и оптимизировать по мере необходимости, но есть некоторые случаи, которые мы извлекаем из опыта и знакомств, когда более узкие соответствуют шаблону.
(Между прочим, если вы можете просто использовать
private static volatile Singleton instance = new Singleton()
или, возможно, просто не использовать синглтоны, а вместо этого использовать статический класс, оба лучше в отношении этих проблем).источник
volatile
это не обязательно, но должно бытьreadonly
. См. Stackoverflow.com/q/12159698/428724 .Причина в производительности. Если
instance != null
(что будет всегда, кроме самого первого раза), нет необходимости делать дорогостоящиеlock
: два потока, одновременно обращающиеся к инициализированному синглтону, будут синхронизироваться безуспешно.источник
Почти в каждом случае (то есть: во всех случаях, кроме самых первых),
instance
не будет null. Получение блокировки обходится дороже, чем простая проверка, поэтому однократная проверка значения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; } } }
источник
Вы можете с радостью создать поточно-ориентированный экземпляр Singleton, в зависимости от потребностей вашего приложения, это краткий код, хотя я бы предпочел ленивую версию @andasa.
public sealed class Singleton { private static readonly Singleton instance = new Singleton(); private Singleton() { } public static Singleton Instance() { return instance; } }
источник
Это называется механизмом блокировки с двойной проверкой, сначала мы проверим, создан ли экземпляр. Если нет, то только мы синхронизируем метод и создадим экземпляр. Это резко повысит производительность приложения. Выполнение блокировки тяжело. Поэтому, чтобы избежать блокировки, нам нужно сначала проверить нулевое значение. Это также потокобезопасность и лучший способ добиться максимальной производительности. Обратите внимание на следующий код.
public sealed class Singleton { private static readonly object Instancelock = new object(); private Singleton() { } private static Singleton instance = null; public static Singleton GetInstance { get { if (instance == null) { lock (Instancelock) { if (instance == null) { instance = new Singleton(); } } } return instance; } } }
источник
Другая версия Singleton, в которой следующая строка кода создает экземпляр Singleton во время запуска приложения.
private static readonly Singleton singleInstance = new Singleton();
Здесь CLR (Common Language Runtime) позаботится об инициализации объекта и безопасности потоков. Это означает, что нам не потребуется явно писать какой-либо код для обеспечения безопасности потоков в многопоточной среде.
public sealed class Singleton { private static int counter = 0; private Singleton() { counter++; Console.WriteLine("Counter Value " + counter.ToString()); } private static readonly Singleton singleInstance = new Singleton(); public static Singleton GetInstance { get { return singleInstance; } } public void PrintDetails(string message) { Console.WriteLine(message); } }
из основного:
static void Main(string[] args) { Parallel.Invoke( () => PrintTeacherDetails(), () => PrintStudentdetails() ); Console.ReadLine(); } private static void PrintTeacherDetails() { Singleton fromTeacher = Singleton.GetInstance; fromTeacher.PrintDetails("From Teacher"); } private static void PrintStudentdetails() { Singleton fromStudent = Singleton.GetInstance; fromStudent.PrintDetails("From Student"); }
источник
Отражательный узор Singleton:
public sealed class Singleton { public static Singleton Instance => _lazy.Value; private static Lazy<Singleton, Func<int>> _lazy { get; } static Singleton() { var i = 0; _lazy = new Lazy<Singleton, Func<int>>(() => { i++; return new Singleton(); }, () => i); } private Singleton() { if (_lazy.Metadata() == 0 || _lazy.IsValueCreated) throw new Exception("Singleton creation exception"); } public void Run() { Console.WriteLine("Singleton called"); } }
источник