Глава.30 .Гибридные.конструкции.синхронизации.потоков
попытка доступа к любому из них приведет к появлению объекта
Singleton
. Не-
которые разработчики обходят данную проблему при помощи вложенных классов.
Рассмотрим третий способ создания одиночного объекта
Singleton
:
internal sealed class Singleton {
private static Singleton s_value = null;
// Закрытый конструктор не дает коду вне данного
// класса создавать экземпляры
private Singleton() {
// Код инициализации объекта Singleton
}
// Открытый статический метод, возвращающий объект Singleton
// (и создающий его, если это нужно)
public static Singleton GetSingleton() {
if (s_value != null) return s_value;
// Создание нового объекта Singleton и превращение его в корень,
// если этого еще не сделал другой поток
Singleton temp = new Singleton();
Interlocked.CompareExchange(ref s_value, temp, null);
// При потере этого потока второй объект Singleton
// утилизируется сборщиком мусора
return s_value; // Возвращение ссылки на объект
}
}
При одновременном вызове метода
GetSingleton
различными потоками в этой
версии кода может появиться два (и более) объекта
Singleton
. Однако метод
Interlocked.CompareExchange
гарантирует публикацию в поле
s_value
только
одной ссылки. Любой объект, не превращенный этим полем в корневой, будет
утилизирован при первой же сборке мусора. Впрочем, в большинстве приложе-
ний практически никогда не возникает ситуация одновременного вызова метода
GetSingleton
разными потоками, поэтому там вряд ли когда-нибудь появится
более одного объекта
Singleton
.
Возможно, вас беспокоит возможность создания множественных объектов
Singleton
, но данный код имеет массу достоинств. Во-первых, он очень быстро ра-
ботает. Во-вторых, в нем никогда не блокируются потоки. Ведь когда поток из пула
блокируется на объекте
Monitor
или на любой другой конструкции синхронизации
потоков режима ядра, пул порождает еще один поток, чтобы загрузить процессор.
Выделяется и инициализируется дополнительная память, а все библиотеки полу-
чают уведомление о присоединении нового потока. С методом
CompareExchange
такого никогда не происходит. Разумеется, данную технику можно использовать
только при отсутствии побочных эффектов у конструктора.
В FCL существует два типа, реализующие описанные в данном разделе шаблоны
программирования. Вот как выглядит обобщенный класс
System.Lazy
(некоторые
методы не показаны):
879
Блокировка.с.двойной.проверкой
public class Lazy {
public Lazy(Func valueFactory, LazyThreadSafetyMode mode);
public Boolean IsValueCreated { get; }
public T Value { get; }
}
А вот как он работает:
public static void Main() {
// Создание оболочки отложенной инициализации для получения DateTime
Lazy s = new Lazy(
() => DateTime.Now.ToLongTimeString(),
LazyThreadSafetyMode.PublicationOnly);
Console.WriteLine(s.IsValueCreated); // Возвращается false, так как
// запроса к Value еще не было
Console.WriteLine(s.Value); // Вызывается этот делегат
Console.WriteLine(s.IsValueCreated); // Возвращается true, так как
// был запрос к Value
Thread.Sleep(10000); // Ждем 10 секунд и снова
// выводим время
Console.WriteLine(s.Value); // Теперь делегат НЕ вызывается,
// результат прежний
}
После запуска данного кода я получил:
False
2:40:42 PM
True
2:40:42 PM ß Обратите внимание, 10 секунд прошло, а время осталось прежним
Код сконструировал экземпляр класса
Lazy
и передал ему один из флагов
LazyThreadSafetyMode
. Вот как выглядят и что означают данные флаги:
public enum LazyThreadSafetyMode {
None, // Безопасность в отношении потоков не
// поддерживается (хорошо для GUI-приложений)
ExecutionAndPublication, // Используется блокировка с двойной проверкой
PublicationOnly, // Используется метод Interlocked.CompareExchange
}
В некоторых ситуациях с ограничениями по памяти отсутствует необходимость
в создании экземпляра класса
Lazy
. Вместо этого можно воспользоваться статиче-
скими методами класса
System.Threading.LazyInitializer
. Вот как он выглядит:
public static class LazyInitializer {
// Эти два метода используют Interlocked.CompareExchange
public static T EnsureInitialized(ref T target) where T: class;
public static T EnsureInitialized(
ref T target, Func valueFactory) where T: class;
// Эти два метода передают syncLock в методы Enter и Exit класса Monitor
public static T EnsureInitialized(
продолжение
880
Do'stlaringiz bilan baham: |