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所说,不推荐使用。

时间: 2024-07-30 22:58:57

lock订单号的相关文章

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

1.新建项目(控制台应用程序) 2.新建一个类:OrderIdHelper.cs 1 /// <summary> 2 /// 订单助手 3 /// </summary> 4 class OrderIdHelper 5 { 6 private static readonly object Locker = new object(); 7 private static string _tempId = ""; 8 9 /// <summary> 10 /

不重复订单号生成规则

偶尔在网上看到的,相对比较好的c#端订单号生成规则 public class BillNumberBuilder{ private static object locker = new object(); private static int sn = 0; public static string NextBillNumber(){ lock(locker){ if(sn == 9999999999) sn = 0; else sn++; return DateTime.Now.ToStrin

订单号生成办法

1.Redis Incr public class RedisConnection { private static readonly ConfigurationOptions ConfigurationOptions = ConfigurationOptions.Parse("127.0.0.1" + ":" + "6379"); private static ConnectionMultiplexer _redisConn; private

C# 生成唯一订单号

根据GUID+DateTime.Now.Ticks生产唯一订单号 1 /// <summary> 2 /// 生成唯一数 3 /// </summary> 4 public class UniqueData 5 { 6 private static object obj = new object(); 7 private static int GuidInt { get { return Guid.NewGuid().GetHashCode(); } } 8 private sta

生成唯一订单号 (支持每秒1000个并发)

#region 订单号生成(支持每秒1000个并发) private static object locker = new object(); private static int sn = 0; public static string GetOrderNO() { lock (locker) { if (sn == 999) sn = 0; else sn++; return DateTime.Now.ToString("yyMMddHHmmssfff") + sn.ToStrin

订单号生成规则

前阵子,公司有个电子商务项目,需要生成订单号.当时的考虑很简单,取系统时间加上随机数,或者使用 uniqid() 方法.我们都知道,订单号最基本的要求就是唯一,这个条件必须满足.仔细考虑下上述方法,在顾客购买量少的情况下,订单重复的可能性为零,但是在购买高蜂期生成的订单号重复是很有可能发生的.所以上述方法不可靠,有待强化.在网上找了一番,发现这位同学的想法挺不错的,redtamo,具体的请稳步过去看看,我作简要概述,该方法用上了英文字母.年月日.Unix 时间戳和微秒数.随机数,重复的可能性大大

Thinkphp 生成订单号小案例

Thinkphp 生成订单号小案例小伙伴们在日常的商城项目开发中,都会遇到订单号生成的问题,今天呢思梦PHP就带领大家去解读一下生成订单号的问题!首先,订单号我们要明确它有有3个性质:1.唯一性 2.不可推测性 3.效率性,唯一性和不可推测性不用说了,效率性是指不能频繁的去数据库查询以避免重复.况且满足这些条件的同时订单号还要足够的短.不知道小伙伴们在日常的项目中是否也和我一样去思考过生成订单的一些小问题,可能你也会说,这些东西不用想的那么复杂,其实呢,小编也是同意大家的看法,但是殊不知我们做程

生成订单号

生成订单号,最大的问题是要考虑高并发的时候.在网上找的一些方案往往在这种情况下难以适用,比如,查询订单序列最大数,根据系统时间生成等. 再考虑另一种可行的方案:数据库递增序列,没错,确实能解决高并发的问题,但在集群等情况下对数据库压力比较大,而且有些数据库对递增序列没有很好的支持,同时递增序列的位数是一直增加的,也有可能会超出数据库表字段限制. 最好的方案是用java的uuid工具类生成唯一的字符串,然后再取这个字符串对象的hashcode值,因为字符串的值是唯一的,它的hashcode也是唯一

插入订单并且输出订单号的sql存储过程

--插入订单-- create proc InsertOrders ( @OrderNumber varchar(300), @OrderState varchar(30), @OrderType varchar(30), @OrderTime DateTime, @OrderFirm varchar(300), @OrderMoney decimal(8,2), @SendWayMoney decimal(8,2), @RegisterId int, @CoalitionOrdersId in