在多線程編程中lock(string){...}隱藏的機關


 常見誤用場景:在訂單支付環節中,為了防止用戶不小心多次點擊支付按鈕而導致的訂單重復支付問題,我們用 lock(訂單號) 來保證對該訂單的操作同時只允許一個線程執行。

 

這樣的想法很好,至少比 lock(處理類的private static object)要好,因為lock訂單號想要的效果是只鎖當前1個訂單的操作,而如果lock靜態變量,那就是鎖所有的訂單,就會導致所有的訂單進行排隊,這顯然是不合理的。

 

那么本文開篇說的lock(訂單號)的做法可以實現想要的效果嗎?我們先用一些代碼來還原使用場景。

 

如果忽略用戶信息及其他驗證,那代碼差不多是這樣:

1 public ActionResult PayOrder(string orderNumber)
2 {
3     lock (orderNumber)
4     {
5         //訂單支付,消息通知等耗時的操作
6     }
7     return View("Success");
8 }

 

這樣的代碼看起來好像沒有什么問題,對於lock關鍵字,MSDN上面包括能夠百度到的資料,好像都是說建議不要使用lock(string),而原因都是同一個。以下這段話摘自MSDN關於lock字符串的建議:

由於進程中使用同一字符串的任何其他代碼將共享同一個鎖,所以出現 lock(“myLock”) 問題。

 

這句話隱藏了一個巨大的機關,那就是“同一字符串”。

 

什么叫“同一字符串”?請看代碼:

static void Main(string[] args)
{
    var str1 = "abc";
    var str2 = "abc";
}

請問上面的str1和str2是同一字符串嗎?答案是YES。

 

再看:

static void Main(string[] args)
{
    var str1 = "abc" + 123;
    var str2 = "abc" + 123;
}

上面的str1和str2還是同一字符串嗎?答案就是NO了。

 

好了,再回到我們訂單支付的問題上面來。在我們的代碼中, lock(orderNumber) ,當用戶手滑一不小心多點了幾次,請問每次進入這個action的orderNumber是同一字符串嗎?答案是NO。這就是說

 

上面處理訂單的代碼實際上並沒有起到任何lock的作用。

 

實際上,字符串比較分兩種,請看代碼:

static void Main(string[] args)
{
    var str1 = "abc" + 123;
    var str2 = "abc" + 123;
    Console.WriteLine(str1 == str2);
    Console.WriteLine(object.ReferenceEquals(str1, str2));
}

上面的代碼第一行輸出True,第二行輸出False。相信不用我解釋你也明白MSDN所說的“同一字符串”了。

 

最佳解決方案

首先,要感謝 Treenew Lyn大神提供的最佳鎖定字符串的解決方案,超簡單:

lock(string.Intern(key))
{
    //...do something
}

 

參考項目:

請用微信掃一掃,關注天天記賬體驗

 


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com