常見誤用場景:在訂單支付環節中,為了防止用戶不小心多次點擊支付按鈕而導致的訂單重復支付問題,我們用 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 }
參考項目:
請用微信掃一掃,關注天天記賬體驗。