常见误用场景:在订单支付环节中,为了防止用户不小心多次点击支付按钮而导致的订单重复支付问题,我们用 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. 在Delphi中使用C++对象(两种方法,但都要改造C++提供的DLL)

    Delphi是市场上最好的RAD工具,但是现在C++占据着主导地位,有时针对一个问题很难找到Delphi或Pascal的解决方案.可是却可能找到了一个相关的C++类.本文描述几种在Delphi代码中使 ...

  2. QT实现不规则窗体

    看到网上有很多不规则窗体的实现,效果很酷.于是使用QT也实现了一个,QT的不规则窗体实现非常简单,只需要设置一个mask(遮掩)图片,这个图片的格式可以使用png或bmp格式,我使用了png格式,默认 ...

  3. DotNetBar怎样控制窗口样式

    DotNetBar怎样控制窗口样式 老帅  在C#中使用控件DevComponents.DotNetBar时,怎样创建一个美丽的窗口.并控制窗口样式呢? 1.新建一个DotNetBar窗口       ...

  4. [Android学习笔记]自定义控件的使用

    自定义控件时,最好抽象得彻底,并且编写需严谨,因为可能程序中多处都会引用到它,或者提供给团队中的其他人使用. 其一般步骤为: 1.创建控件的类文件,定义其功能逻辑.一般继承自现有控件或者View2.在 ...

  5. SQL SERVER CHARINDEX函数

    CHARINDEX函数经常常使用来在一段字符中搜索字符或者字符串.假设被搜索的字符中包括有要搜索的字符,那么这个函数返回一个非零的整数,这个整数是要搜索的字符在被搜索的字符中的開始位数.即CHARIN ...

  6. java Process在windows的使用汇总(转)

    最常用的是ant(java工程中流行),maven,及通用的exec(只要有shell脚本如.sh,.bat,.exe,.cmd等).而其实前两者不容易出错,后者却遇到了以下问题:Caused by: ...

  7. CreateThread、_beginthreadex和AfxBeginThread 的区别

    CreateThread._beginthreadex和AfxBeginThread 创建线程好几个函数可以使用,可是它们有什么区别,适用于什么情况呢?参考了一些资料,写得都挺好的,这里做一些摘抄和整 ...

  8. (step 8.2.13)hdu 1524(A Chess Game)

    题目大意 : 在一个 有向无环图顶点上面有几个棋子, 2个人轮流操作, 每次操作就是找一个棋子往它能够移 动的地方移动一格, 不能操作的人输. 输入第一行 为一个 N , 表示有 N 个顶点 0 -& ...

  9. APPCAN学习笔记001---app高速开发AppCan.cn平台概述

    1.APPCAN学习笔记---app高速开发AppCan.cn平台概述 1. 平台概述 技术qq交流群:JavaDream:251572072 AppCan.cn开发平台是基于HTML5技术的跨平台移 ...

  10. hdu 1542 Atlantis(段树&amp;扫描线&amp;面积和)

    Atlantis Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total S ...