• c#多线程同步之lock


       一提起lock,想必大家都很熟悉,因为它易用,顾名思义,就是一把锁,常用于多线程的同步,一次只允许一个线程进入。最近遇到一个很诡异的bug。

      

     1         private static readonly object lock4 = new object();
     2 
     3         private static void LoadResolvers(string name)
     4         {
     5             if (resolvesCache.Count == 0)
     6             {
     7 
     8 #if DEBUG
     9                 Console.WriteLine(name+",进入第一层判断,当前解析器数:" + resolvesCache.Count + ",时间:" + DateTime.Now.ToShortTimeString());
    10 #endif
    11 
    12                 lock (lock4)
    13                 {
    14 
    15                     if (resolvesCache.Count == 0)
    16                     {
    17 
    18 #if DEBUG
    19                         Console.WriteLine(name + ",进入第二层判断,当前解析器数:" + resolvesCache.Count + ",时间:" + DateTime.Now.ToShortTimeString());
    20 #endif
    21 
    22 
    23                         List<Resolvers> listResolvers = ResolversBLL.GetAllResolvers();
    24 
    25 
    26 #if DEBUG
    27                         Console.WriteLine(name + ",解析器查询完,准备遍历,查询出的解析器数:" + listResolvers.Count+ ",当前解析器数:" + resolvesCache.Count + ",时间:" + DateTime.Now.ToShortTimeString());
    28 #endif
    29 
    30                         foreach (Resolvers resolver in listResolvers)
    31                         {
    32                             List<ResolverConfigures> listConfigures = ResolverConfiguresBLL.GetResolverConfiguresByResolverId(resolver.ResolverID);
    33 
    34                             LoginInfo loginInfo = LoginInfoDAL.SelectItemByItemId(resolver.ResolverID);
    35 
    36                             NameValueCollection valueCollection = new NameValueCollection();
    37 
    38                             if (loginInfo != null)
    39                             {
    40                                 valueCollection.Add("username", loginInfo.UserName);
    41                                 valueCollection.Add("password", loginInfo.Password);
    42                             }
    43 
    44                             foreach (ResolverConfigures configures in listConfigures)
    45                             {
    46                                 if (!string.IsNullOrEmpty(configures.Key))
    47                                     valueCollection.Add(configures.Key, configures.Value);
    48                             }
    49 
    50                             if (!resolvesCache.ContainsKey(resolver))
    51                             {
    52                                 resolvesCache.Add(resolver, valueCollection);
    53                             }
    54                         }
    55 
    56 #if DEBUG
    57                         Console.WriteLine(name + ",遍历完解析器并添加完成,当前解析器数:" + resolvesCache.Count + ",时间:" + DateTime.Now.ToShortTimeString());
    58 #endif
    59 
    60                     }
    61                 }
    62             }
    63         }

         这段代码的大意:从数据库中查询出解析器(23行)加入到解析器缓存中(52行)。这个牵扯到多线程,因此,第12行加了把锁。本来数据库中只有13条数据,但是软件启动后,缓存中添加了26条数据,这是为什么呢?明明double  if判断,lock每次只允许一个线程进入。

                                                                图1

         为了搞清楚事情的真相,我写了个控制台代码:

     1  private static Dictionary<string, string> resolvesCache = new Dictionary<string, string>();
     2         public static Dictionary<string, string> ResolvesCache
     3         {
     4             get
     5             {
     6                 if (resolvesCache.Count == 0)
     7                 {
     8                     LoadResolvers();
     9                 }
    10 
    11                 return resolvesCache;
    12             }
    13             set
    14             {
    15                 resolvesCache = value;
    16             }
    17         }
    18 
    19         private static void LoadResolvers()
    20         {
    21             if (resolvesCache.Count == 0)
    22             {
    23                 lock (resolvesCache)
    24                 {
    25                     Thread.Sleep(new Random().Next(1000, 3000));
    26 
    27                     for (int i = 0; i < 13; i++)
    28                     {
    29                         string key = i + DateTime.Now.Millisecond.ToString();
    30 
    31                         if (!resolvesCache.ContainsKey(key))
    32                         {
    33                             resolvesCache.Add(key, "wbq");
    34                         }
    35                     }
    36                 }
    37             }
    38         }
    39 
    40 
    41 
    42         static void Main(string[] args)
    43         {
    44 
    45             Thread thread1 = new Thread(new ThreadStart(() =>
    46                 {
    47                     Console.WriteLine("线程1:" + ResolvesCache.Count.ToString());
    48 
    49                 }));
    50 
    51             thread1.Start();
    52 
    53 
    54             Thread thread2 = new Thread(new ThreadStart(() =>
    55             {
    56 
    57                 LoadResolvers();
    58                 Console.WriteLine("线程2:" + ResolvesCache.Count.ToString());
    59 
    60             }));
    61 
    62             thread2.Start();
    63 
    64 
    65             Thread thread3 = new Thread(new ThreadStart(() =>
    66             {
    67                 LoadResolvers();
    68                 Console.WriteLine("线程3:" + ResolvesCache.Count.ToString());
    69 
    70             }));
    71 
    72             thread3.Start();

    运行结果:

    每次一个线程访问下缓存,缓存数据加倍变化,这是为什么呢?哦,别忘了double if判断。因为程序刚运行,三个线程几乎同时到达22行,过了第一个if。这好比,很多人在公司外面等着面试,大家赶时间点几乎同时到,但是面试是一对一进行,这时候需要等待。在24行之后,再加一个if判断:

    运行结果:

    这下跟正式代码一样了吧。都是double if 判断,测试代码达到要求了,为什么正式代码有问题呢?为了研究,在正式的代码上加上了好多debug,让它输出当前线程名称,记录相关日志。

    从图1的日志上可以看出,这是同一个线程所为。为什么会执行两遍呢?

    再看看23行代码:

    List<Resolvers> listResolvers = ResolversBLL.GetAllResolvers();

    跟进到 ResolversBLL类中,发现了一句代码:

     private static OfficialMetadataResolveManager resolverManager = new OfficialMetadataResolveManager(BibliographyAutoUpdateProcess.ResolvesCache);

    静态对象,类加载的时候,首先访问。 这不是解析器缓存的访问器吗?看看它的实现:

     1  private static Dictionary<Resolvers, NameValueCollection> resolvesCache = new Dictionary<Resolvers, NameValueCollection>();
     2 
     3         public static Dictionary<Resolvers, NameValueCollection> ResolvesCache
     4         {
     5             get
     6             {
     7                 if (resolvesCache.Count == 0)
     8                 {
     9                     LoadResolvers(Thread.CurrentThread.Name);
    10                 }
    11 
    12                 return resolvesCache;
    13             }
    14             set
    15             {
    16                 resolvesCache = value;
    17             }
    18         }

    第9行调用了 LoadResolvers,当前线程正在执行LoadResolvers方法,中途调用解析器缓存访问器,结果解析器缓存访问器又调用了此访问。所以这段代码执行了两次,因此,数据翻倍。终于真相大白了。

    要修改其实也很简单,把第二个if判断,放到数据库查询解析器之后即可。这样的话,等于数据库查询了两次,但是缓存中只缓存一份数据。

  • 相关阅读:
    常用HTTP Contenttype头信息及文件类型对照
    Web开发专用IDE:Aptana Studio 3.0
    如果张柏芝的孩子长大
    Apache日志分析工具Awstats的安装和配置
    台北貢寮~三貂角燈塔
    [转]ASP,PHP,JSP,ASP.NET 比较
    联想 Thinkpad SL410使用体验
    2011年6月TIOBE编程语言走势图及指数
    百度搜索能力越来越差了,看下图
    PHP 5.3.x中弃用的特性 Deprecated
  • 原文地址:https://www.cnblogs.com/wangqiang3311/p/8067080.html
Copyright © 2020-2023  润新知