常见误用场景:在订单支付环节中,为了防止用户不小心多次点击支付按钮而导致的订单重复支付问题,我们用 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. oracle检查点队列与增量检查点【转载】

    oracle检查点队列与增量检查点 今天是2013-09-04,这几天一直心里安顿不下来,今天还好了,可以自己安静的学习一下oracle,在此记录一下学习笔记.这篇文章我不知道在那转载的,一直都留在我 ...

  2. AdaBoost中利用Haar特征进行人脸识别算法分析与总结1——Haar特征与积分图

    原地址:http://blog.csdn.net/watkinsong/article/details/7631241 目前因为做人脸识别的一个小项目,用到了AdaBoost的人脸识别算法,因为在网上 ...

  3. [IDEs]Eclipse设置花括号样式

    用惯Vistual Studio,在使用Eclipse时发现有很多东西还是挺不习惯,第一个就要解决花括号的样式 步骤: 1.Windows->Preferences->Java->C ...

  4. C++获取文件大小常用技巧

    C++编程语言在程序开发应用中能够帮助我们轻松的完成许多功能需求.比如今天为大家介绍的C++获取文件大小的方法,就可以以多种方式轻松的实现.现在将会实现方法呈现给大家,以便大家参考. C++获取文件大 ...

  5. Iterator 和 Iterable 差别和联系

    用Iterator模式实现遍历集合  Iterator模式是用于遍历集合类的标准訪问方法.它能够把訪问逻辑从不同类型的集合类中抽象出来,从而避免向client暴露集合的内部结构. 比如,假设没有使用I ...

  6. AFNetworking2.0后 进行Post请求

    本文以新浪微博的Oauth认证为样例进行Post请求的演示 以下直接上代码: #import "ViewController.h" #import "AFNetworki ...

  7. CodeForce 356A Knight Tournament(set应用)

     Knight Tournament time limit per test 3 seconds memory limit per test 256 megabytes input standard ...

  8. Andorid Clip 实现自定义的进度条效果实例

    Android该系统提供了一个水平进度条为我们展现了运行使用进展情况,水平进度条显示用于运行进度Clip Drawable技术 下面我们通过一个具体的例子来说明Clip Drawable使用. 还有我 ...

  9. 谨记给UpdatePanel中动态添加的控件赋ID

    原文:谨记给UpdatePanel中动态添加的控件赋ID 昨天下定决 心对上次做的布局编辑器控件加以改进,其中最主要变化的就是要完全使用ASP.NET AJAX!但是很遗憾,虽然耳闻已久,但目前对AS ...

  10. 在Java中怎样逐行地写文件?

    下边是写东西到一个文件里的Java代码. 执行后每一次,一个新的文件被创建,而且之前一个也将会被新的文件替代.这和给文件追加内容是不同的. public static void writeFile1( ...