877
Блокировка.с.двойной.проверкой
Можно ожидать, что компилятор создаст код, выделяющий память под объект
Singleton
, вызовет конструктор для инициализации полей данного объекта и при-
своит ссылку на него полю
s_value
, чтобы это значение увидели другие потоки — это
называется
публикацией
(publishing). Однако компилятор может выделить память
под объект
Singleton
, назначить ссылку переменной
s_value
(выполнив публи-
кацию) и только после этого вызвать конструктор.
Если в процедуре участвует
всего один поток, подобное изменение очередности операций не имеет значения.
Но что произойдет, если после публикации ссылки в поле
s_value
, но до вызова
конструктора другой поток вызовет метод
GetSingleton
? Этот поток обнаружит, что
значение поля
s_value
отлично от
null
и начнет пользоваться объектом
Singleton
,
хотя его конструктор еще не закончил работу! Подобную
ошибку крайне сложно
отследить, особенно из-за того, что время ее появления случайно.
Эту проблему решает вызов метода
Interlocked.Exchange
. Он гарантирует, что
ссылка из переменной
temp
будет опубликована в поле
s_value
только после того,
как конструктор завершит свою работу. Альтернативным способом решения про-
блемы
является пометка поля
s_value
ключевым словом
volatile
. Запись в такое
волатильное (неустойчивое) поле
s_value
возможна только после завершения
конструктора. К сожалению, то же самое относится ко всем процедурам чтения во-
латильного поля, а так как никакой необходимости в этом нет, вряд ли стоит идти
на снижение производительности без полной уверенности в полезности этого.
В начале этого раздела я назвал блокировку с двойной проверкой не особо инте-
ресной. С моей точки зрения, разработчики используют это решение гораздо чаще,
чем следовало бы. В большинстве случаев оно только снижает производительность.
Вот гораздо
более простая версия класса
Singleton
с аналогичным предыдущей
версии поведением, но без блокирования с двойной проверкой:
internal sealed class Singleton {
private static Singleton s_value = new Singleton();
// Закрытый конструктор не дает коду вне данного класса
// создавать экземпляры
private Singleton() {
// Код инициализации объекта Singleton
}
// Открытый статический метод, возвращающий объект Singleton
// (и создающий его, если это нужно)
public static Singleton GetSingleton() { return s_value; }
}
Так как CLR автоматически вызывает конструктор класса при первой по-
пытке получить доступ к члену этого класса, при первом запросе потока к методу
GetSingleton
класса
Singleton
автоматически создается экземпляр объекта. Более
того, среда CLR гарантирует безопасность в отношении потоков при вызове кон-
структора класса. Все это уже объяснялось в главе 8.
Недостатком такого подхода
является вызов конструктора типа при первом доступе к любому члену класса.
То есть если в типе
Singleton
определить другие статические члены, первая же