常见误用场景:在订单支付环节中,为了防止用户不小心多次点击支付按钮而导致的订单重复支付问题,我们用 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所说的“同一字符串”了。

最后,再分享一个我们项目中用来解决lock(订单号)的方案。

调用方法:

public ActionResult PayOrder(string orderNumber)
{
Locker.Run(orderNumber, () =>
{
//订单支付,消息通知等耗时的操作
});
return View("Success");
}

用到的Locker类:

 1 public class Locker
2 {
3 private const int ExpireMinutes = 10;
4
5 private static readonly Timer _timer;
6 private static readonly Dictionary<string, LockObj> _dict = new Dictionary<string, LockObj>();
7
8 static Locker()
9 {
10 _timer = new Timer(60000);
11 _timer.Elapsed += (s, e) =>
12 {
13 RemovedExired();
14 };
15 _timer.Start();
16 }
17
18 public static void Run(string key, Action action)
19 {
20 LockObj lockObj = null;
21 lock (_dict)
22 {
23 if (!_dict.ContainsKey(key))
24 {
25 _dict[key] = new LockObj();
26 }
27 lockObj = _dict[key];
28 lockObj.Time = DateTime.Now;
29 }
30 lock (lockObj)
31 {
32 action();
33 }
34 }
35
36 public static void RemovedExired()
37 {
38 lock (_dict)
39 {
40 var keys = _dict.Where(x => x.Value.IsExpired()).Select(x => x.Key).ToList();
41 foreach (var key in keys)
42 {
43 _dict.Remove(key);
44 }
45 }
46 }
47
48 private class LockObj
49 {
50 public DateTime Time { private get; set; }
51
52 public bool IsExpired()
53 {
54 return this.Time < DateTime.Now.AddMinutes(-ExpireMinutes);
55 }
56 }
57 }

总结

lock(字符串)其实最大的用处就是类似锁定当前订单的操作,lock一个常量字符串就没有多大意义,正如MSDN所说,不推荐使用。

lock订单号的更多相关文章

  1. C#:lock锁与订单号(或交易号)的生成

    在弄电商类网站的时候,往往是根据年月日时分秒的格式生成订单号(yyyyMMddHHmmss),为了解决并发性,就直接在生成订单号的区域块加上lock. 下面,我们来简单测试一下. 1.新建项目(控制台 ...

  2. C#生成唯一不重复订单号帮助类

    1.使用场景 通常,在做一些表单的功能时,需要生成唯一不重复的订单单号,本文提供的帮助类可以适合大多数场景的单号生成使用,拿来即用,方便快捷无重复.而且,在高并发的情况下也是可以使用的. 之前看到有人 ...

  3. 偶尔在网上看到的,相对比较好的c#端订单号生成规则

    偶尔在网上看到的,相对比较好的c#端订单号生成规则 public class BillNumberBuilder{     private static object locker = new obj ...

  4. 微信JSApi支付~订单号和微信交易号

    返回目录 谈谈transactionId和out_trade_no 前一篇微信JSApi支付~坑和如何填坑文章反映不错,所以又写了个后篇,呵呵. 每个第三方在线支付系统中都会有至少两类订单号,其一为支 ...

  5. php生成唯一订单号

    支持更改长度/** * 生成唯一订单号 * */ function build_order_no(){ return date('Ymd').substr(implode(NULL, array_ma ...

  6. iOS-微信支付(订单号重复的问题)

    1. 官方文档中说过同一笔交易不能多次提交,出现这个错误让核实商户订单号是否重复提交,但是有些情况下是需要重复提交的,比如:用户微信支付的时候没有付款,直接取消了,那么订单如果已经创建了,在订单中心就 ...

  7. php:订单号和时区

    1.php制作订单号 $data['orderid'] = date("YmdHis") . settype(rand(100000, 999999), string) ; 2.p ...

  8. logback日志项目使用方法 - 150205交易模块添加日志信息logback,orderNo订单号为log主键便于跟踪,数字常量化,解决取消支付BUG,弱网络环境原因

    1.项目里面的日志,便于跟踪数据的变更和异常错误信息产生.生产环境的日志级别是INFO,测试环境日志级别DEBUG,如果生产环境的日志级别是DEBUG,虽然方便查询问题,可以看到SQL语句等信息,但是 ...

  9. PHP生成订单号(产品号+年的后2位+月+日+订单号)

    require '../common.inc.php'; /* * 产品号+年的后2位+月+日+订单数 * @param [Int] $prodcutId 产品号 * @param [Int] $tr ...

随机推荐

  1. Python语法

  2. HashMap的工作原理(转)

    HashMap的工作原理是近年来常见的Java面试题.几乎每个Java程序员都知道HashMap,都知道哪里要用HashMap,知道Hashtable和HashMap之间的区别,那么为何这道面试题如此 ...

  3. 深入浅出Node.js (1) - Node简介

    1.1 Node的诞生历程 1.2 Node的命名与起源 1.2.1 为什么是JavaScript 1.2.2 为什么叫Node 1.3 Node给JavaScript带来的意义 1.4 Node的特 ...

  4. Houdini Pyro流体的插值变速

    用简单的节点尝试了下Houdini流体的变速,这里的流体指的是Pyro,而不是FLIP.FLIP仅仅须要记录ID属性然后TimeBlend & TimeShift就可以. Vimeo 上图是一 ...

  5. vim经常使用命令总结

    vim 选择文本,删除,复制,粘贴   文本的选择,对于编辑器来说,是非常主要的东西,也常常被用到,总结例如以下: v    从光标当前位置開始,光标所经过的地方会被选中,再按一下v结束. V     ...

  6. 每个Android开发者必须知道的资源集锦

    英文原文:Resources every Android developer must know 随着 Android 平台持续惊人的增长,越来越多的开发人员开始工作于 Android 应用程序.而且 ...

  7. Nginx 负载均衡-加权轮询策略剖析

    本文介绍的是客户端请求在多个后端服务器之间的均衡,注意与客户端请求在多个nginx进程之间的均衡相区别(Nginx根据每个工作进程的当前压力调整它们获取监听套接口的几率,那些当前比较空闲的工作进程有更 ...

  8. oracle 之 内存—鞭辟近里(三)

    oracle 之 内存—鞭辟近里(三) 今天是2013-07-08,今天晚上突然接到一个电话,我的外甥问我的qq是多少,我感觉很吃惊,他长大了.在他现在这个年龄就开始接触网络,我难免有少许担心,希望他 ...

  9. SCU 3133(博弈)

    传送门:windy和水星 -- 水星游戏 2 题意:在一张由 n*m 的格子组成的棋盘上放着 k 个骑士每个骑士的位置为(xi,yi),表示第xi行,第yi列骑士如果当前位置为(x,y),一步可以走的 ...

  10. 【Android小应用】颈椎保健操Android开源项目

    前段时间在知乎上回答已入 IT 行业的前辈,有哪些关于保护身体健康的知识分享给 IT 新人? ,回复了一张图片,评论里面有知友希望通过程序可以实现,我后面尝试着通过程序实现了效果,现开源出来,大家可以 ...