Чрезмерная проверка нулевого поля в моем синглтоне?

Код ниже представляет одноэлемент, который я использую в своем приложении. Предположим, что _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последующие запросы будут возвращать тот же экземпляр.

Другие вопросы по тегам