lock订单号
常见误用场景:在订单支付环节中,为了防止用户不小心多次点击支付按钮而导致的订单重复支付问题,我们用 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订单号的更多相关文章
- C#:lock锁与订单号(或交易号)的生成
在弄电商类网站的时候,往往是根据年月日时分秒的格式生成订单号(yyyyMMddHHmmss),为了解决并发性,就直接在生成订单号的区域块加上lock. 下面,我们来简单测试一下. 1.新建项目(控制台 ...
- C#生成唯一不重复订单号帮助类
1.使用场景 通常,在做一些表单的功能时,需要生成唯一不重复的订单单号,本文提供的帮助类可以适合大多数场景的单号生成使用,拿来即用,方便快捷无重复.而且,在高并发的情况下也是可以使用的. 之前看到有人 ...
- 偶尔在网上看到的,相对比较好的c#端订单号生成规则
偶尔在网上看到的,相对比较好的c#端订单号生成规则 public class BillNumberBuilder{ private static object locker = new obj ...
- 微信JSApi支付~订单号和微信交易号
返回目录 谈谈transactionId和out_trade_no 前一篇微信JSApi支付~坑和如何填坑文章反映不错,所以又写了个后篇,呵呵. 每个第三方在线支付系统中都会有至少两类订单号,其一为支 ...
- php生成唯一订单号
支持更改长度/** * 生成唯一订单号 * */ function build_order_no(){ return date('Ymd').substr(implode(NULL, array_ma ...
- iOS-微信支付(订单号重复的问题)
1. 官方文档中说过同一笔交易不能多次提交,出现这个错误让核实商户订单号是否重复提交,但是有些情况下是需要重复提交的,比如:用户微信支付的时候没有付款,直接取消了,那么订单如果已经创建了,在订单中心就 ...
- php:订单号和时区
1.php制作订单号 $data['orderid'] = date("YmdHis") . settype(rand(100000, 999999), string) ; 2.p ...
- logback日志项目使用方法 - 150205交易模块添加日志信息logback,orderNo订单号为log主键便于跟踪,数字常量化,解决取消支付BUG,弱网络环境原因
1.项目里面的日志,便于跟踪数据的变更和异常错误信息产生.生产环境的日志级别是INFO,测试环境日志级别DEBUG,如果生产环境的日志级别是DEBUG,虽然方便查询问题,可以看到SQL语句等信息,但是 ...
- PHP生成订单号(产品号+年的后2位+月+日+订单号)
require '../common.inc.php'; /* * 产品号+年的后2位+月+日+订单数 * @param [Int] $prodcutId 产品号 * @param [Int] $tr ...
随机推荐
- GNU GPL介绍
怎样在程序中使用GNU许可证 不管使用哪种许可证,使用时须要在每一个程序的源文件里加入两个元素:一个版权声明和一个复制许可声明.说明该程序使用GNU许可证进行授权.另外在声明版权前应该说明 ...
- 14.4.3.1 The InnoDB Buffer Pool
14.4.3.1 The InnoDB Buffer Pool 14.4.3.2 Configuring Multiple Buffer Pool Instances 14.4.3.3 Making ...
- ORALCE 之LRU链与脏LRU链【转载】
今天是2013-09-09,时别n久的一篇经典文章,有被我在google发现了,再次转载一下.学习一下. 一.LRU链: 任何缓存的大小都是有限制的,并且总不如被缓存的数据多.就像Buffer cac ...
- winform下载网页代码
1:webClient client=new WebClient(); client.Downloadstring(地址) client.Downloadfile(地址,保存路径) 2:后台线程dow ...
- WCF(1)----服务创建
本例中,我们通过一个关于Camera的服务为例子来说明WCF的开发流程,该服务比较简单,只是用来实现对Camera的添加,枚举,删除等操作. 详细步骤如下: 1:创建一个WCF Service Lib ...
- js编码、解码
js对文字进行编码涉及3个函数:escape,encodeURI,encodeURIComponent,相应3个解码函数:unescape,decodeURI,decodeURIComponent 1 ...
- Java Swing界面编程(31)---菜单条:JMenu
package com.beyole.test; import javax.swing.JFrame; import javax.swing.JMenu; import javax.swing.JMe ...
- CSS3 Media Query实现响应Web设计(宽度为不同的移动设备)
现在的屏幕分辨率.小到320px(iPhone),大到2560px或甚至更高(大显示屏).范围内变化很大.除了使用传统的台式机.用户会越来越多的通过手机.上网本.iPad一类的平板设备来浏览页面. 这 ...
- FOJ 1591 —— Coral的烦恼
#include<stdio.h> int main() { __int64 n,i,sum,l,r; while(scanf("%I64d",&n)!=EOF ...
- 【iOS开发-76】Private Contacts案例:导航控制器使用、数据传递、第三方类库使用、tableViewCell的加入删除、数据存储等
(1)效果 (2)源码与第三方类库下载 http://download.csdn.net/detail/wsb200514/8155979 (3)总结 --导航控制器,能够直接用代码的push和pop ...