在多线程编程中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 }
参考项目:
请用微信扫一扫,关注天天记账体验。