Thread Safe С# Singleton Pattern
У меня есть несколько вопросов относительно шаблона синглтона, как описано здесь: 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;
}
}
Дорогое выполнение блокировки?
Ответы
Ответ 1
Выполнение блокировки ужасно дорого по сравнению с простой проверкой указателя instance != null
.
Образец, который вы видите здесь, называется дважды проверенной блокировкой. Его цель состоит в том, чтобы избежать дорогостоящей операции блокировки, которая будет нужна только один раз (когда односторонний доступ к первому доступу). Реализация такова, потому что она также должна гарантировать, что при инициализации синглтона не будет ошибок, вызванных условиями гонки нитей.
Подумайте об этом так: голая проверка null
(без lock
) гарантированно даст вам правильный полезный ответ, только если этот ответ "да, объект уже построен". Но если ответ "еще не построен", то у вас недостаточно информации, потому что вы действительно хотели знать, что он "еще не сконструирован, и ни одна другая нить не собирается строить его в ближайшее время". Таким образом, вы используете внешнюю проверку как очень быстрый первоначальный тест, и вы инициируете правильную, без ошибок, но "дорогостоящую" процедуру (блокировка и проверка), только если ответ "нет".
Вышеприведенная реализация достаточно хороша для большинства случаев, но в этот момент неплохо перейти и прочитать статья Джона Скита о синглонах в С# который также оценивает другие альтернативы.
Ответ 2
ленивая версия:
public sealed class Singleton
{
static readonly Lazy<Singleton> lazy = new Lazy<Singleton>(() => new Singleton());
private Singleton() { }
public static Singleton Instance => lazy.Value;
}
Требуется .NET 4 и С# 6.0 (VS2015) или новее.
Ответ 3
Выполнение блокировки: довольно дешево (еще дороже, чем нулевой тест).
Выполнение блокировки, когда другой поток имеет это: вы получаете стоимость того, что им еще нужно делать при блокировке, добавленной в свое время.
Выполнение блокировки, когда другой поток имеет ее, и на нее также ждут десятки других потоков: Crippling.
По соображениям производительности вы всегда хотите иметь блокировки, которые требуется другому потоку, в течение самого короткого периода времени.
Конечно, легче рассуждать о "широких" блокировках, чем узких, поэтому стоит начинать с них широким и оптимизировать по мере необходимости, но есть некоторые случаи, которые мы узнаем из опыта и знакомства, где более узкий подход к шаблону.
(Кстати, если вы можете просто использовать private static volatile Singleton instance = new Singleton()
или если вы просто не можете использовать одиночные игры, но вместо этого используйте статический класс, то оба лучше подходят для этих проблем).
Ответ 4
Причина - производительность. Если instance != null
(который всегда будет иметь место, за исключением самого первого раза), нет необходимости делать дорогостоящий lock
: два потока, обращающихся к инициализированному синглтону одновременно, будут синхронизироваться без изменений.
Ответ 5
Почти в каждом случае (то есть: все случаи, кроме самых первых), instance
не будет равно нулю. Приобретение блокировки является более дорогостоящим, чем простая проверка, поэтому проверка после значения instance
перед блокировкой - хорошая и бесплатная оптимизация.
Этот шаблон называется блокировкой с двойной проверкой: http://en.wikipedia.org/wiki/Double-checked_locking
Ответ 6
Джеффри Рихтер рекомендует следующее:
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;
}
}
}
Ответ 7
Вы можете охотно создать потокобезопасный экземпляр Singleton, в зависимости от потребностей вашего приложения, это лаконичный код, хотя я бы предпочел @andasa lazy version.
public sealed class Singleton
{
private static readonly Singleton instance = new Singleton();
private Singleton() { }
public static Singleton Instance()
{
return instance;
}
}
Ответ 8
Другая версия 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");
}
Ответ 9
Это называется механизмом двойной проверки блокировки, во-первых, мы проверим, создан ли экземпляр или нет. Если нет, то только мы синхронизируем метод и создаем экземпляр. Это значительно улучшит производительность приложения. Выполнение блокировки тяжело. Поэтому, чтобы избежать блокировки, сначала нужно проверить нулевое значение. Это также потокобезопасно, и это лучший способ добиться максимальной производительности. Пожалуйста, посмотрите на следующий код.
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;
}
}
}