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

  • 相关阅读:
    基于GIS的生态环境信息一张图系统应用与研究
    基于GIS的开源社区(村)网格化数据管理平台
    智慧乡村综合解决方案数字化智能农业
    开源免费乡村振兴数字乡村平台
    Oracle连接字符串记录
    SqlLocalDB使用笔记
    Orchard分类Taxonomies图文教程
    自己手动maven命令将jar包安装到本地。
    Mysql如何去掉数据库中重复记录?
    常见的数据库方面问题
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/5219745.html
Copyright © 2020-2023  润新知