Первоначальные комментарии
Я не обязательно думаю, что автор этой статьи на самом деле предлагал использовать этот вариант шаблона блокировки с двойной проверкой как таковой. Я думаю, что он просто указал, что это один вариант, который наивный разработчик может рассмотреть для решения проблемы в контексте типов значений.
Типы значений, очевидно, не могут хранить значения null
, поэтому необходимо использовать другую переменную для оповещения о завершении инициализации. Автор упоминает все это, а затем смущает разговоры о чтении instance
как null
. Предположительно, автор думал о действительно наивном разработчике, который однажды неправильно использовал этот вариант для типов значений, а затем продолжал применять его, также неправильно, для ссылочных типов. В случае типа значения поток может прочитать и использовать struct
с инициализацией поля по умолчанию, когда это не было предназначено. В случае ссылочных типов поток может читать и использовать null
экземпляр.
Использование Thread.VolatileRead
было предложено автором для исправления этой вариации. Без изменчивого чтения чтение instance
в операторе возврата может быть отменено до чтения initialized
, как это.
class Singleton
{
private static object slock = new object();
private static Singleton instance;
private static int initialized;
private Singleton() {}
public Instance {
get {
var local = instance;
if (initialized == 0) {
lock (slock) {
if (initialized == 0) {
instance = new Singleton();
initialized = 1;
}
}
}
return local;
}
}
}
Надеюсь, приведенное выше изменение порядка кода наглядно демонстрирует проблему. И столь же очевидно, что изменчивое чтение initialized
предотвращает снятие чтения instance
.
И снова, я думаю, что автор просто показал один из возможных способов исправить этот конкретный вариант, а не то, что автор защищал этот подход в целом.
Ответы на ваши вопросы
У меня такой вопрос, разве не существует опасность того, что
заказана?
ДА (квалифицировано): Как вы правильно указали, записи в instance
и initialized
можно поменять местами внутри lock
. Хуже того, записи, которые могут происходить внутри Singleton.ctor
, также могут происходить не по порядку, так что instance
назначается до , когда экземпляр полностью инициализируется. Другой поток может увидеть instance
set, но этот экземпляр может быть в частично сконструированном состоянии.
Однако записи в реализации Microsoft CLI имеют семантику ограничения выпуска. То есть все, что я только что сказал, неприменимо при использовании среды выполнения .NET Framework на любой аппаратной платформе. Но такая непонятная среда, как Mono, работающая на ARM , может демонстрировать проблемное поведение.
Использование автором Thread.VolatileRead
для «исправления» этого варианта в общем случае не сработает, поскольку оно не решает проблему переупорядоченных записей. Код не является на 100% переносимым. Это одна из причин, почему я сомневаюсь, что автор предлагал этот вариант.
Каноническая вариация использования одной переменной instance
в сочетании с volatile
, очевидно, является правильным решением. Ключевое слово volatile
имеет семантику acqu-fence при чтениях и семантику release-fence при записи, поэтому оно решает обе проблемы; тот, который вы определили, и тот, к которому относится статья.