Чрезмерная проверка нулевого поля в моем синглтоне?
Код ниже представляет одноэлемент, который я использую в своем приложении. Предположим, что _MyObject = New Object
представляет собой очень дорогой вызов базы данных, который я не хочу делать более одного раза ни при каких обстоятельствах. Чтобы этого не произошло, я сначала проверяю, _MyObject
вспомогательное поле является нулевым. Если это так, я врываюсь в SyncLock, чтобы гарантировать, что только один поток может войти сюда одновременно. Однако в случае, если два потока пройдут первую нулевую проверку до того, как будет создан экземпляр синглтона, поток B окажется в SyncLock, а поток A создаст экземпляр. После того как поток A выйдет из блокировки, поток B войдет в блокировку и заново создаст экземпляр, что приведет к выполнению этого дорогостоящего вызова базы данных. Чтобы предотвратить это, я добавил дополнительную нулевую проверку поля поддержки, которое происходит внутри блокировки. Таким образом, если поток B завершит ожидание блокировки, он пройдет и выполнит еще одну нулевую проверку, чтобы убедиться, что он не воссоздает экземпляр.
Так действительно ли необходимо сделать две нулевые проверки? Будет ли избавление от внешней нулевой проверки и просто начать с Synclock таким же? Другими словами, является ли блокировка потоков переменной так же быстро, как позволить нескольким потокам одновременно обращаться к полю поддержки? Если это так, внешняя нулевая проверка является излишней.
Private Shared synclocker As New Object
Private Shared _MyObject As Object = Nothing
Public Shared ReadOnly Property MyObject As Object
Get
If _MyObject Is Nothing Then 'superfluous null check?
SyncLock synclocker
If _MyObject Is Nothing Then _MyObject = New Object
End SyncLock
End If
Return _MyObject
End Get
End Property
2 ответа
Вы абсолютно правы, добавив внутренний If
заявление (у вас все равно было бы состояние гонки без него, как вы правильно заметили).
Вы также правы в том, что с чисто логической точки зрения внешняя проверка является излишней. Однако внешняя нулевая проверка позволяет избежать относительно дорогих SyncLock
операция.
Подумайте: если вы уже создали свой синглтон и случайно ударили по своему свойству сразу из 10 потоков, внешний If
это то, что мешает этим 10 потокам стоять в очереди и практически ничего не делать. Синхронизация потоков не из дешевых, и поэтому If
для производительности, а не для функциональности.
Это, вероятно, будет лучше, чем ответ, а не комментарий.
Итак, используя Lazy для реализации "сделай дорогую операцию только один раз, чем верни ссылку на созданный экземпляр":
Private Shared _MyObject As Lazy(Of Object) = New Lazy(Of Object)(AddressOf InitYourObject)
Private Shared Function InitYourObject() As Object
Return New Object()
End Function
Public Shared ReadOnly Property MyObject As Object
Get
Return _MyObject.Value
End Get
End Property
Это очень простой и потокобезопасный способ выполнения однократной инициализации по требованию. InitYourObject
Метод обрабатывает любую инициализацию, которую вам нужно сделать, и возвращает экземпляр созданного класса. При первом запросе метод инициализации вызывается при вызове _MyObject.Value
последующие запросы будут возвращать тот же экземпляр.