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

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

这段代码的大意:从数据库中查询出解析器(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 == )
  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 == )
  22. {
  23. lock (resolvesCache)
  24. {
  25. Thread.Sleep(new Random().Next(, ));
  26.  
  27. for (int i = ; i < ; 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. static void Main(string[] args)
  41. {
  42.  
  43. Thread thread1 = new Thread(new ThreadStart(() =>
  44. {
  45. Console.WriteLine("线程1:" + ResolvesCache.Count.ToString());
  46.  
  47. }));
  48.  
  49. thread1.Start();
  50.  
  51. Thread thread2 = new Thread(new ThreadStart(() =>
  52. {
  53.  
  54. LoadResolvers();
  55. Console.WriteLine("线程2:" + ResolvesCache.Count.ToString());
  56.  
  57. }));
  58.  
  59. thread2.Start();
  60.  
  61. Thread thread3 = new Thread(new ThreadStart(() =>
  62. {
  63. LoadResolvers();
  64. Console.WriteLine("线程3:" + ResolvesCache.Count.ToString());
  65.  
  66. }));
  67.  
  68. thread3.Start();

运行结果:

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

运行结果:

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

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

再看看23行代码:

  1. List<Resolvers> listResolvers = ResolversBLL.GetAllResolvers();
  2.  
  3. 跟进到 ResolversBLL类中,发现了一句代码:
  4.  
  5.  private static OfficialMetadataResolveManager resolverManager = new OfficialMetadataResolveManager(BibliographyAutoUpdateProcess.ResolvesCache);
  6. 静态对象,类加载的时候,首先访问。 这不是解析器缓存的访问器吗?看看它的实现:
  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 == )
  8. {
  9. LoadResolvers(Thread.CurrentThread.Name);
  10. }
  11.  
  12. return resolvesCache;
  13. }
  14. set
  15. {
  16. resolvesCache = value;
  17. }
  18. }

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

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

c#多线程同步之lock的更多相关文章

  1. 多线程同步工具——Lock

    本文原创,转载请注明出处. 参考文章: <"JUC锁"03之 公平锁(一)> <"JUC锁"03之 公平锁(二)> 锁分独占锁与共享锁, ...

  2. python多线程同步机制Lock

    #!/usr/bin/env python# -*- coding: utf-8 -*- import threadingimport time value = 0lock = threading.L ...

  3. c#中多线程同步Lock(锁)的研究以及跨线程UI的操作

    本文只针对C#中,多线程同步所用到的锁(lock)作为研究对象.由于想更直观的显示结果,所以,在做demo的时候,就把多线程通过事件操作UI的代码也写了出来,留作备忘和分享吧. 其实多线程的同步,使用 ...

  4. python 多线程中的同步锁 Lock Rlock Semaphore Event Conditio

    摘要:在使用多线程的应用下,如何保证线程安全,以及线程之间的同步,或者访问共享变量等问题是十分棘手的问题,也是使用多线程下面临的问题,如果处理不好,会带来较严重的后果,使用python多线程中提供Lo ...

  5. 通过Lock对象以及Condition对象实现多线程同步

    通过Lock对象以及Condition对象实现多线程同步: 在之前的学习中,无论是通过synchronized建立同步代码块,还是通过synchronized建立同步函数,都是把对象看成一把锁来实现同 ...

  6. 读写锁(read-write lock)机制-----多线程同步问题的解决

    原文: http://blog.chinaunix.net/uid-27177626-id-3791049.html ----------------------------------------- ...

  7. c#中多线程同步Lock(锁)的研究以及跨线程UI的操作 (转)

    https://www.cnblogs.com/tommyheng/p/4104552.html 本文只针对C#中,多线程同步所用到的锁(lock)作为研究对象.由于想更直观的显示结果,所以,在做de ...

  8. python笔记9 线程进程 threading多线程模块 GIL锁 multiprocessing多进程模块 同步锁Lock 队列queue IO模型

    线程与进程 进程 进程就是一个程序在一个数据集上的一次动态执行过程.进程一般由程序.数据集.进程控制块三部分组成.我们编写的程序用来描述进程要完成哪些功能以及如何完成:数据集则是程序在执行过程中所需要 ...

  9. C# 多线程同步和线程通信

    多线程通信 1. 当线程之间有先后的依赖关系时,属于线程之间的通信问题.也就是后一个线程要等待别的一个或多个线程全部完成,才能开始下一步的工作.可以使用: WaitHandle Class WaitH ...

随机推荐

  1. 关于 JS 拖拽功能的冲突问题及解决方法

    前言 我在之前写过关于 JS 拖拽的文章,实现方式和网上能搜到的方法大致相同,别无二致,但是在一次偶然的测试中发现,这种绑定事件的方式可能会和其它的拖拽事件产生冲突,由此产生了对于事件绑定的思考.本文 ...

  2. CodeForces - 796C Bank Hacking

    思路:共有n-1条边连接n个点,即形成一棵树.一开始需要选择一个点hack--将这个点视为根结点,与它相邻的点防御值加1,与它相隔一个在线点的点的防御也加1.当根节点被hack,即这个点被删除,又变成 ...

  3. Java NIO之缓冲区

    1.简介 Java NIO 相关类在 JDK 1.4 中被引入,用于提高 I/O 的效率.Java NIO 包含了很多东西,但核心的东西不外乎 Buffer.Channel 和 Selector.这其 ...

  4. JQuery基础知识学习1

    1.JQuery是javascript的类库 2.下载JQuery 3.导入JQuery <script src="jquery-3.0.0.js"></scri ...

  5. Storm日志分析调研及其实时架构

    1.Storm第一个Demo 2.Windows下基于eclipse的Storm应用开发与调试 3.Storm实例+mysql数据库保存 4.Storm原理介绍 5. flume+kafka+stor ...

  6. 沉淀,再出发——在Ubuntu Kylin15.04中配置Hadoop单机/伪分布式系统经验分享

    在Ubuntu Kylin15.04中配置Hadoop单机/伪分布式系统经验分享 一.工作准备 首先,明确工作的重心,在Ubuntu Kylin15.04中配置Hadoop集群,这里我是用的双系统中的 ...

  7. 学习PHP的必备开发工具

    对于PHP开发者,在互联网上有很多可用的开发工具,但对于初学者不知道哪个php开发工具比较好,找到一个合适的PHP开发工具是很难的,需要花费很多的时间精力.所以,今天常青春工作室就为初学者推荐几个最好 ...

  8. (2018干货系列一)最新Java学习路线整合

    怎么学Java Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承.指针等概念,因此Java语言具有功能强大和简单易用两个特征. 话不多说,直接上干货: ...

  9. ImportError: No module named 'xlrd' 解决办法

    import pandas as pd data = pd.read_excel('工作簿1.xls',sheetname='Sheet1') 用pandas读取Excel文件时,会提示 Import ...

  10. xp+WinDBG+VMware调试内核

    呵呵,搞点突兀的标题而已.其实说的还是如何使用WinDBG和VMware来搭建调试内核的环境而已,这些网上已经有数不清的教程了,不过我喜欢自己亲手写一下.第一,把这个过程写一遍能加深印象,就算以后忘记 ...