1

下面的代码代表我在我的应用程序中使用的单例。让我们假设它_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
4

2 回答 2

2

作为答案而不是评论,这可能会更好。

因此,使用 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,后续请求将返回相同的实例。

于 2014-06-30T14:28:56.310 回答
1

添加内部If语句是绝对正确的(正如您正确指出的那样,如果没有它,您仍然会有竞争条件)。

您也是正确的,从纯逻辑的角度来看,外部检查是多余的。但是,外部空值检查避免了相对昂贵的SyncLock操作。

考虑一下:如果您已经创建了单例,并且碰巧同时从 10 个线程中访问了您的属性,那么外部If是阻止这 10 个线程排队而基本上什么都不做的原因。同步线程并不便宜,因此添加If是为了性能而不是功能。

于 2014-06-30T11:14:11.163 回答