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

         private static readonly object lock4 = new object();

         private static void LoadResolvers(string name)
{
if (resolvesCache.Count == )
{ #if DEBUG
Console.WriteLine(name+",进入第一层判断,当前解析器数:" + resolvesCache.Count + ",时间:" + DateTime.Now.ToShortTimeString());
#endif lock (lock4)
{ if (resolvesCache.Count == )
{ #if DEBUG
Console.WriteLine(name + ",进入第二层判断,当前解析器数:" + resolvesCache.Count + ",时间:" + DateTime.Now.ToShortTimeString());
#endif List<Resolvers> listResolvers = ResolversBLL.GetAllResolvers(); #if DEBUG
Console.WriteLine(name + ",解析器查询完,准备遍历,查询出的解析器数:" + listResolvers.Count+ ",当前解析器数:" + resolvesCache.Count + ",时间:" + DateTime.Now.ToShortTimeString());
#endif foreach (Resolvers resolver in listResolvers)
{
List<ResolverConfigures> listConfigures = ResolverConfiguresBLL.GetResolverConfiguresByResolverId(resolver.ResolverID); LoginInfo loginInfo = LoginInfoDAL.SelectItemByItemId(resolver.ResolverID); NameValueCollection valueCollection = new NameValueCollection(); if (loginInfo != null)
{
valueCollection.Add("username", loginInfo.UserName);
valueCollection.Add("password", loginInfo.Password);
} foreach (ResolverConfigures configures in listConfigures)
{
if (!string.IsNullOrEmpty(configures.Key))
valueCollection.Add(configures.Key, configures.Value);
} if (!resolvesCache.ContainsKey(resolver))
{
resolvesCache.Add(resolver, valueCollection);
}
} #if DEBUG
Console.WriteLine(name + ",遍历完解析器并添加完成,当前解析器数:" + resolvesCache.Count + ",时间:" + DateTime.Now.ToShortTimeString());
#endif }
}
}
}

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

图1

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

  private static Dictionary<string, string> resolvesCache = new Dictionary<string, string>();
public static Dictionary<string, string> ResolvesCache
{
get
{
if (resolvesCache.Count == )
{
LoadResolvers();
} return resolvesCache;
}
set
{
resolvesCache = value;
}
} private static void LoadResolvers()
{
if (resolvesCache.Count == )
{
lock (resolvesCache)
{
Thread.Sleep(new Random().Next(, )); for (int i = ; i < ; i++)
{
string key = i + DateTime.Now.Millisecond.ToString(); if (!resolvesCache.ContainsKey(key))
{
resolvesCache.Add(key, "wbq");
}
}
}
}
} static void Main(string[] args)
{ Thread thread1 = new Thread(new ThreadStart(() =>
{
Console.WriteLine("线程1:" + ResolvesCache.Count.ToString()); })); thread1.Start(); Thread thread2 = new Thread(new ThreadStart(() =>
{ LoadResolvers();
Console.WriteLine("线程2:" + ResolvesCache.Count.ToString()); })); thread2.Start(); Thread thread3 = new Thread(new ThreadStart(() =>
{
LoadResolvers();
Console.WriteLine("线程3:" + ResolvesCache.Count.ToString()); })); 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);

静态对象,类加载的时候,首先访问。 这不是解析器缓存的访问器吗?看看它的实现:
  private static Dictionary<Resolvers, NameValueCollection> resolvesCache = new Dictionary<Resolvers, NameValueCollection>();

         public static Dictionary<Resolvers, NameValueCollection> ResolvesCache
{
get
{
if (resolvesCache.Count == )
{
LoadResolvers(Thread.CurrentThread.Name);
} return resolvesCache;
}
set
{
resolvesCache = value;
}
}

第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. spring,property not found on type

    刚开始学spring mvc ,遇到这个报错. jsp页面 user 实体类 public class User { private String name; private int age; pri ...

  2. Shiro的原理及Web搭建

    shiro(java安全框架) 以下都是综合之前的人加上自己的一些小总结 Apache Shiro是一个强大且易用的Java安全框架,执行身份验证.授权.密码学和会话管理.使用Shiro的易于理解的A ...

  3. django-表单

    一.从Request对象中获取信息 1.URL相关的信息 属性/方法 说明 举例 request.path 除域名以外的请求路径,以正斜杠开头 "/hello/" request. ...

  4. Shell脚本的基本流程控制

    if else read -p '请输入分数:' score if [ $score -lt 60 ]; then echo '60分以下' elif  [ $score -lt 70 ]; then ...

  5. GB 标准

    std::map<int, std::string> GB2261 = { { 0,"未知的性别" }, { 1,"男性" }, { 2," ...

  6. POJ - 2387 最短路

    思路:用dijkstra算法,是无向图. AC代码: #include <cstdio> #include <cmath> #include <cctype> #i ...

  7. HDU - 4545 字符串处理

    思路:对于每个字符,如果它能被替换一定要优先替换,其次再进行删除.遵循这个策略即可. 证明: 对于这题的第一个测试数据: abba addba 1 d b 当匹配到'b'  和 'd'时应该优先替换而 ...

  8. apue.h头文件(UNIX环境高级编程)

    在看UNIX环境高级编程是,碰到一个头文件"apue.h",搜一下别人的帖子,其实apue.h是作者自己写的一个文件,包含了常用的头文件,系统不自带.其中包含了常用的头文件,以及出 ...

  9. 解决VC6下调不出MSDN的问题!

     原文:http://www.programfan.com/blog/article.asp?id=1524http://blog.programfan.com/trackback.asp?id=15 ...

  10. Windows PowerShell漫谈-win7下没有超级终端

    Windows PowerShell是我在研究win7新特性的时候发现的新工具,起初没有对它产生太大的兴趣,只是简单看看了有关它的介绍.简单使用了一下,感觉上它和cmd.exe没有本质区别.对它产生兴 ...