当我使用双重检查锁定来创建一个线程安全的单例时,我在WPF应用程序中经历了一个非常奇怪的行为。我的代码代表了该技术的通常实现:
Private _graphics As Contracts.Interfaces.Components.IGraphics
ReadOnly Property Graphics As Contracts.Interfaces.Components.IGraphics
Get
If _graphics Is Nothing Then
SyncLock lock
If (_graphics Is Nothing) Then
_graphics = New GFX.Application()
End If
End SyncLock
End If
Return _graphics
End Get
End Property
当我运行程序时,我遇到了异常,因为Application-object构造了两次。我无法相信自己的眼睛,因为我对锁的工作方式的理解告诉我这应该是线程安全的。
我的假设是,这段代码在同一个线程上运行了两次(异步),所以我最后修改了我的代码来分析这个问题。每次线程进入锁定语句时,我都会添加一个List以便将Thread-object放入其中。接下来,我只需要将包含的Thread-object与当前线程进行比较:
ReadOnly Property Graphics As Contracts.Interfaces.Components.IGraphics
Get
If _graphics Is Nothing Then
SyncLock lock
If (_graphics Is Nothing) Then
If (threadList.Any() AndAlso (threadList.First() IsNot Threading.Thread.CurrentThread)) Then
Stop ' Program is stopping here.
End If
threadList.Add(Threading.Thread.CurrentThread)
_graphics = New GFX.Application() ' Object construction takes around 70 ms.
End If
End SyncLock
End If
Return _graphics
End Get
End Property
正如您可能猜到的,程序停止了,因为多个线程遇到了相同的锁定语句。这怎么可能是真的?我对这种行为绝对没有解释,请告诉我你是否有任何想法。感谢。
更新: 我想补充一些细节。这是初始化例程,导致两个匿名方法异步运行。他们每个人都访问Singleton持有人的只读属性。
Task.Factory.StartNew(Sub() Graphics.Init(...)
Task.Factory.StartNew(Sub() Graphics.Run(...)
以下是相关的反汇编指令。也许他们给出了一个指示。这里有人能够遵循这些指示:
If _graphics Is Nothing Then
002D91ED cmp dword ptr ds:[38E3430h],0
002D91F4 sete al
002D91F7 movzx eax,al
002D91FA mov dword ptr [ebp-48h],eax
002D91FD cmp dword ptr [ebp-48h],0
002D9201 je 002D9297
SyncLock lock
002D9207 nop
002D9208 mov eax,dword ptr ds:[038E3438h]
002D920D mov dword ptr [ebp-40h],eax
002D9210 mov ecx,dword ptr [ebp-40h]
002D9213 call 518DCE8C
002D9218 nop
002D9219 xor edx,edx
002D921B mov dword ptr [ebp-44h],edx
002D921E nop
002D921F lea edx,[ebp-44h]
002D9222 mov ecx,dword ptr [ebp-40h]
002D9225 call 7301A2B0
002D922A nop
If _graphics Is Nothing Then
002D922B cmp dword ptr ds:[38E3430h],0
002D9232 sete al
002D9235 movzx eax,al
002D9238 mov dword ptr [ebp-48h],eax
002D923B cmp dword ptr [ebp-48h],0
002D923F je 002D9264
_graphics = New GFX.Application()
002D9241 mov ecx,47D664h
002D9246 call 73E601D2
002D924B mov dword ptr [ebp-4Ch],eax
002D924E mov ecx,dword ptr [ebp-4Ch]
002D9251 call 002D8C10
002D9256 mov eax,dword ptr [ebp-4Ch]
002D9259 lea edx,ds:[38E3430h]
002D925F call 73E52360
End If
002D9264 nop
End SyncLock