最近公司的項目中發現一個編譯優化導致的bug。同事敘述為“在CPU開啟out-of-order execution優化時,是有bug的”。針對這個問題,比較好的優化方法如下:
private static JobManager self;
private static object asyncObj = new object();
public static JobManager Instance
{
get
{
if (self == null)
{
lock (asyncObj)
{
if (self == null)
{
// 正確的實現方法應該為: var temp = new JobManager(); Interlocked.Exchange(ref self, temp);
self = new JobManager();
}
}
}
return self;
}
}
這里需要解釋一下:
self = new JobManager()
這句你的本意是為 JobManager 分配內存,調用構造器初始化字段,再將引用賦給 self ,即發布出來讓其他線程可見。但是,那只是你一廂情願的想法,編譯器可能這樣做:為JobManager 分配內存,將引用發布到(賦給)self,再調用構造器。然而,如果在將引用發布給 self 之后,調用構造器之前,另一個線程發現 self 不為 null,便開始使用JobManager對象,這時會發生什么?這個時候對象的構造器還沒有執行結束!這是一個很難追蹤的bug。
從雙檢鎖技術的角度來看,使用
Interlocked.Exchange確實是最好的解決方案。但有兩個問題,它該如何解決?
1.速度是否夠快?
2.如果一個線程池線程在Monitor的線程同步構造上阻塞,線程池會創建另一個線程來保持CPU的“飽和”,而創建一個新線程的代價是很昂貴的,我們該如何避免這樣的情況?
試着跳出“lock+2次if”的框子,我們可以使用
Interlocked.CompareExchange來解決上面的問題。下面是一個示例:
internal sealed class MySingleton
{
private static MySingleton s_value = null;
public static MySingleton GetMySingleton()
{
if (s_value != null) return s_value;
MySingleton temp = new MySingleton();
Interlocked.CompareExchange(ref s_value, temp, null);
return s_value;
}
}
雖然多個線程同時調用GetMySingleton,會創建2個或者更多的MySingleton對象,但沒有被s_value引用的臨時對象會在以后被垃圾回收。大多數應用程序很少會發生同時調用GetMySingleton的情況,所以不太可能出現創建多個MySingleton對象的情況。上述代碼帶來優勢是很明顯的,首先,它的速度是非常快,其次,它永不阻塞線程。這就解決了前面在雙檢鎖技術中提出的問題。
另外,在.net 4.0中提供了2個類型封裝上述兩種模式(雙檢鎖技術、使用
Interlocked.CompareExchange技術):
泛型
System.Lazy類和
System.Threading.LazyInitializer類。下面是2個示例:
public static void Main()
{
Lazy<string> s = new Lazy<string>(() => DateTime.Now.ToLongTimeString(), LazyThreadSafetyMode.PublicationOnly);
Console.WriteLine(s.IsValueCreated);
Console.WriteLine(s.Value);
Console.WriteLine(s.IsValueCreated);
Thread.Sleep(5000);
Console.WriteLine(s.Value);
Console.WriteLine(DateTime.Now.ToLongTimeString());
}
輸出結果:

public static void Main()
{
string name = null;
LazyInitializer.EnsureInitialized(ref name, () => "Benjamin");
Console.WriteLine(name);
LazyInitializer.EnsureInitialized(ref name, () => "Yao");
Console.WriteLine(name);
}
輸出結果:

其中枚舉
LazyThreadSafetyMode解釋如下:
public enum LazyThreadSafetyMode
{
None = 0, //完全沒有線程安全劫持(適合GUI應用程序)
PublicationOnly = 1, //使用Interlocked.CompareExchange技術
ExecutionAndPublication = 2, //使用雙檢鎖技術
}
