下面的代码表示我在我的应用程序中使用的单例。让我们假设_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
答案 0 :(得分: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
时会调用初始化方法,后续请求将返回相同的实例。
答案 1 :(得分:1)
你添加内部If
声明是完全正确的(正如你所正确指出的那样,如果没有它,你仍会有竞争条件)。
你也是正确的,从纯逻辑的角度来看,外部检查是多余的。 但是,外部空检查可以避免相对昂贵的SyncLock
操作。
考虑一下:如果你已经创建了你的单例,并且你碰巧同时从10个线程中击中了你的属性,那么外部If
就是阻止那10个线程排队到什么都没做的东西。同步线程并不便宜,因此添加的If
用于提高性能而非功能。