波安搬。。。 http://www.cnblogs.com/wolf-sun/p/4209521.html

---------------------------------------------------------------------------------------------------------------------------------

lock到底要锁谁?

答:访问谁所谁,锁在哪儿? 要看对称时间

----------------------------------------------------- 分割线 ------------------------------------------------------------------

写在前面

最近一个月一直在弄文件传输组件,其中用到多线程的技术,但有的地方确实需要只能有一个线程来操作,如何才能保证只有一个线程呢?首先想到的就是锁的概念,最近在我们项目组中听的最多的也是锁谁,如何锁?看到有同事使用lock(this),也有lock(private static object),那就有点困惑了,lock到底锁谁才是最合适的呢?

lock

首先先上官方Msdn的说法

lock 关键字可确保当一个线程位于代码的临界区时,另一个线程不会进入该临界区。 如果其他线程尝试进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。
lock 关键字在块的开始处调用 Enter,而在块的结尾处调用 Exit。 ThreadInterruptedException 引发,如果 Interrupt 中断等待输入 lock 语句的线程。
通常,应避免锁定 public 类型,否则实例将超出代码的控制范围。

常见的结构 lock (this)、lock (typeof (MyType)) 和 lock ("myLock") 违反此准则:
如果实例可以被公共访问,将出现 lock (this) 问题。
如果 MyType 可以被公共访问,将出现 lock (typeof (MyType)) 问题。
由于进程中使用同一字符串的任何其他代码都将共享同一个锁,所以出现 lock("myLock") 问题。
最佳做法是定义 private 对象来锁定, 或 private static 对象变量来保护所有实例所共有的数据。
在 lock 语句的正文不能使用 等待 关键字。

Enter指的是Monitor.Enter(获取指定对象上的排他锁。),Exit指的是Monitor.Exit(释放指定对象上的排他锁。)

有上面msdn的解释及Exit方法,可以这样猜测“直到该对象被释放”,”该对象“应该是指锁的对象,对象释放了或者对象改变了,其他的线程才可以进入代码临界区(是不是可以这样来理解?)。

在多线程中,每个线程都有自己的资源,但是代码区是共享的,即每个线程都可以执行相同的函数。这可能带来的问题就是几个线程同时执行一个函数,导致数据的混乱,产生不可预料的结果,因此我们必须避免这种情况的发生。

打个比方,有这样一个情景,很多公司所在的大厦的厕所的蹲位都是小单间型的,也就是一次只能进去一个人,那么为了避免每次进去一个人,那怎么做呢?不就是一个人进去之后顺手把门锁上么?这样你在里面干啥事,外边的人也只能等待你解放完了,才能进入。而蹲位的资源(蹲位,手纸等)是共享的。

最常使用的锁是如下格式的代码段:

  1. private static object objlock = new object();
  2. lock (objlock )
  3. {
  4. //要执行的代码逻辑
  5. }

为什么锁的对象是私有的呢?还是以厕所为例子吧,私有就好比,这把锁只有你能访问到,而且最好这把锁不会因为外力而有所改变,别人访问不到,这样才能保证你进去了,别人就进不去了,如果是公有的,就好比你蹲位小单间的锁不是安装在里面而是安装在外边的,别人想不想进就不是你所能控制的了,这样也不安全。

lock(this)

通过字面的意思就是锁的当前实例对象。那是否对其他实例对象产生影响?那下面看一个例子:

  1. 1 namespace Wolfy.LockDemo
  2. 2 {
  3. 3 class Program
  4. 4 {
  5. 5 static void Main(string[] args)
  6. 6 {
  7. 7 Test t = new Test();
  8. 8 Test t2 = new Test();
  9. 9 Thread[] threads = new Thread[10];
  10. 10 for (int i = 0; i < threads.Length; i++)
  11. 11 {
  12. 12 //通过循环创建10个线程。
  13. 13 threads[i] = new Thread(() =>
  14. 14 {
  15. 15 t2.Print();
  16. 16 });
  17. 17 //为每个线程设置一个名字
  18. 18 threads[i].Name = "thread" + i;
  19. 19
  20. 20 }
  21. 21 //开启创建的十个线程
  22. 22 for (int i = 0; i < threads.Length; i++)
  23. 23 {
  24. 24 threads[i].Start();
  25. 25 }
  26. 26
  27. 27 Console.Read();
  28. 28 }
  29. 29 }
  30. 30 class Test
  31. 31 {
  32. 32 public void Print()
  33. 33 {
  34. 34 lock (this)
  35. 35 {
  36. 36 for (int i = 0; i < 5; i++)
  37. 37 {
  38. 38 Console.WriteLine("\t" + Thread.CurrentThread.Name.ToString() + "\t" + i.ToString() + " ");
  39. 39 }
  40. 40 }
  41. 41 }
  42. 42 }
  43. 43 }

如果在不加锁的情况下输出如下:

从上面的输出结果也可以看出,线程出现了争抢的现象,而这并不是我们想要的结果,我们想要的是,每次只有一个线程去执行Print方法。那我们就尝试一下lock(this)

  1. 1 class Test
  2. 2 {
  3. 3 public void Print()
  4. 4 {
  5. 5 lock (this)
  6. 6 {
  7. 7 for (int i = 0; i < 5; i++)
  8. 8 {
  9. 9 Console.WriteLine("\t" + Thread.CurrentThread.Name.ToString() + "\t" + i.ToString() + " ");
  10. 10 }
  11. 11 }
  12. 12 }
  13. 13 }

输出结果

从输出结果,觉得大功告成了,可是现在情况又来了,在项目中的其他的地方,有同事也这样写了这样的代码,又创建了一个Test对象,而且他也知道使用多线程执行耗时的工作,那么就会出现类似下面的代码。

  1. 1 namespace Wolfy.LockDemo
  2. 2 {
  3. 3 class Program
  4. 4 {
  5. 5 static void Main(string[] args)
  6. 6 {
  7. 7 Test t = new Test();
  8. 8 Test t2 = new Test();
  9. 9 t2.Age = 20;
  10. 10 Thread[] threads = new Thread[10];
  11. 11 for (int i = 0; i < threads.Length; i++)
  12. 12 {
  13. 13 //通过循环创建10个线程。
  14. 14 threads[i] = new Thread(() =>
  15. 15 {
  16. 16 t.Print();
  17. 17 t2.Print();
  18. 18 });
  19. 19 //为每个线程设置一个名字
  20. 20 threads[i].Name = "thread" + i;
  21. 21
  22. 22 }
  23. 23
  24. 24
  25. 25 //开启创建的十个线程
  26. 26 for (int i = 0; i < threads.Length; i++)
  27. 27 {
  28. 28 threads[i].Start();
  29. 29 }
  30. 30
  31. 31 Console.Read();
  32. 32 }
  33. 33 }
  34. 34 class Test
  35. 35 {
  36. 36 public int Age { get; set; }
  37. 37 public void Print()
  38. 38 {
  39. 39 lock (this)
  40. 40 {
  41. 41 for (int i = 0; i < 5; i++)
  42. 42 {
  43. 43 Console.WriteLine("\t" + Thread.CurrentThread.Name.ToString() + "\t" + i.ToString() + " ");
  44. 44 }
  45. 45 }
  46. 46 }
  47. 47 }
  48. 48 }

这里为Test加了一个Age属性,为了区别当前创建的对象不是同一个对象。

输出的结果为

在输出的结果中已经出现了线程抢占执行的情况了,而不是一个线程执行完另一个线程在执行。

lock(private obj)

那么我们现在使用一个全局的私有的对象试一试。

  1. 1 namespace Wolfy.LockDemo
  2. 2 {
  3. 3 class Program
  4. 4 {
  5. 5 private static object objLock = new object();
  6. 6 static void Main(string[] args)
  7. 7 {
  8. 8 Test t = new Test();
  9. 9 Test t2 = new Test();
  10. 10 t2.Age = 20;
  11. 11 Thread[] threads = new Thread[10];
  12. 12 for (int i = 0; i < threads.Length; i++)
  13. 13 {
  14. 14 //通过循环创建10个线程。
  15. 15 threads[i] = new Thread(() =>
  16. 16 {
  17. 17 lock (objLock)
  18. 18 {
  19. 19 t.Print();
  20. 20 t2.Print();
  21. 21 }
  22. 22 });
  23. 23 //为每个线程设置一个名字
  24. 24 threads[i].Name = "thread" + i;
  25. 25
  26. 26 }
  27. 27
  28. 28
  29. 29 //开启创建的十个线程
  30. 30 for (int i = 0; i < threads.Length; i++)
  31. 31 {
  32. 32 threads[i].Start();
  33. 33 }
  34. 34
  35. 35 Console.Read();
  36. 36 }
  37. 37 }
  38. 38 class Test
  39. 39 {
  40. 40 public int Age { get; set; }
  41. 41 public void Print()
  42. 42 {
  43. 43 for (int i = 0; i < 5; i++)
  44. 44 {
  45. 45 Console.WriteLine("\t" + Thread.CurrentThread.Name.ToString() + "\t" + i.ToString() + " ");
  46. 46 }
  47. 47 }
  48. 48 }
  49. 49 }

输出的结果

从输出的结果也可以看出,有序的,每次进来一个线程执行。

那通过上面的比较可以有这样的一个结论,lock的结果好不好,还是关键看锁的谁,如果外边能对这个谁进行修改,lock就失去了作用。所以一般情况下,使用静态的并且是只读的对象。

也就有了类似下面的代码

  1. 1 private static readonly object objLock = new object();

你可能会说,不对啊,你下面的代码跟上面的代码不一样啊,为什么就得出这样的结论?难道就不能把Object放在test类中么,放在test类中的话,在new Test()的时候,其实放在Test中也是可以的,只要保证objLock在外部是无法修改的就可以。

上面说的最多的是lock对象,那么它能不能lock值类型?

答案是否定的,如

当然lock(null)也是不行的,如图

虽然编译可以通过,但是运行就会出错。

lock(string)

string也是应用类型,从语法上来说是没有错的。

但是锁定字符串尤其危险,因为字符串被公共语言运行库 (CLR)“暂留”。 这意味着整个程序中任何给定字符串都只有一个实例,就是这同一个对象表示了所有运行的应用程序域的所有线程中的该文本。因此,只要在应用程序进程中的任何位置处具有相同内容的字符串上放置了锁,就将锁定应用程序中该字符串的所有实例。通常,最好避免锁定 public 类型或锁定不受应用程序控制的对象实例。例如,如果该实例可以被公开访问,则 lock(this) 可能会有问题,因为不受控制的代码也可能会锁定该对象。这可能导致死锁,即两个或更多个线程等待释放同一对象。出于同样的原因,锁定公共数据类型(相比于对象)也可能导致问题。而且lock(this)只对当前对象有效,如果多个对象之间就达不到同步的效果。lock(typeof(Class))与锁定字符串一样,范围太广了。

总结

关于lock的介绍就到这里,有下面几点需要注意的地方

1、lock的是引用类型的对象,string类型除外。

2、lock推荐的做法是使用静态的、只读的、私有的对象。

3、保证lock的对象在外部无法修改才有意义,如果lock的对象在外部改变了,对其他线程就会畅通无阻,失去了lock的意义。

说说lock到底要锁谁?的更多相关文章

  1. [C#基础]说说lock到底锁谁?

    写在前面 最近一个月一直在弄文件传输组件,其中用到多线程的技术,但有的地方确实需要只能有一个线程来操作,如何才能保证只有一个线程呢?首先想到的就是锁的概念,最近在我们项目组中听的最多的也是锁谁,如何锁 ...

  2. C# 说说lock到底锁谁?(1)

    写在前面 最近一个月一直在弄文件传输组件,其中用到多线程的技术,但有的地方确实需要只能有一个线程来操作,如何才能保证只有一个线程呢?首先想到的就是锁的概念,最近在我们项目组中听的最多的也是锁谁,如何锁 ...

  3. C# 说说lock到底锁谁?(2)

    摘要 今天在园子里面有园友反馈关于[C#基础]说说lock到底锁谁?文章中lock(this)的问题.后来针对文章中的例子,仔细想了一下,确实不准确,才有了这篇文章的补充,已经对文章中的demo进行修 ...

  4. [C#基础]说说lock到底锁谁?(补充与修改)

    摘要 今天在园子里面有园友反馈关于[C#基础]说说lock到底锁谁?文章中lock(this)的问题.后来针对文章中的例子,仔细想了一下,确实不准确,才有了这篇文章的补充,已经对文章中的demo进行修 ...

  5. 说说lock到底锁谁(II)?

    摘要 今天在园子里面有园友反馈关于[C#基础]说说lock到底锁谁?文章中lock(this)的问题.后来针对文章中的例子,仔细想了一下,确实不准确,才有了这篇文章的补充,已经对文章中的demo进行修 ...

  6. 说说lock到底锁谁(I)?

    写在前面 最近一个月一直在弄文件传输组件,其中用到多线程的技术,但有的地方确实需要只能有一个线程来操作,如何才能保证只有一个线程呢?首先想到的就是锁的概念,最近在我们项目组中听的最多的也是锁谁,如何锁 ...

  7. [转载] java并发编程:Lock(线程锁)

    作者:海子 原文链接: http://www.cnblogs.com/dolphin0520/p/3923167.html 出处:http://www.cnblogs.com/dolphin0520/ ...

  8. python之GIL官方文档 global interpreter lock 全局解释器锁

    0.目录 2. 术语 global interpreter lock 全局解释器锁3. C-API 还有更多没有仔细看4. 定期切换线程5. wiki.python6. python.doc FAQ ...

  9. Lock()与RLock()锁

    资源总是有限的,程序运行如果对同一个对象进行操作,则有可能造成资源的争用,甚至导致死锁 也可能导致读写混乱 锁提供如下方法: 1.Lock.acquire([blocking]) 2.Lock.rel ...

随机推荐

  1. android使用友盟实现第三方登录、分享以及微信回调无反应问题解决办法

    这里介绍微信和新浪登录.微信登录和新浪登录都需要申请第三方账号.可以参考官方文档http://dev.umeng.com/social/android/operation#2还是很清晰的. 新浪微博开 ...

  2. VUE 动态切换列表active样式

    参考VUE官方文档样式绑定 https://cn.vuejs.org/v2/guide/class-and-style.html 需求是动态加载出来了所有菜单列表,点击其中一个li元素改变这个元素的背 ...

  3. linux环境下查看tomcat日志

    1.先切换到:cd usr/local/tomcat5/logs 2.tail -f catalina.out 3.这样运行时就可以实时查看运行日志 Ctrl+c 是退出tail命令. alt+E+R ...

  4. Python 网络编程之网络协议(未完待续)

    一:网络编程从两大架构开始 1.网络开发的两大架构 c/s 架构 : client  server B/S 架构 : Brower  server (1)bs 和 cs 架构之间的关系? (2)哪一种 ...

  5. 《ORACLE数据库管理与开发》第三章学习之常用函数记录

    <ORACLE数据库管理与开发>第三章学习之常用函数记录 注:文章中的*代表所要操作的列名 1.lower(*)/upper(*),将此列下的值转为小写/大写 2.initcap(*):把 ...

  6. burpsite 和jdk的配置

    最近小白再安装工具,首先是java的jdk,小白的电脑重装系统之后以前装的就没有了,然后记性不好的小白就开始百度了,百度上说是需要配置java_home和classpath路径然后再去编辑path路径 ...

  7. ROS常用库(四)API学习之常用common_msgs(下)

    一.前言 承接ROS常用库(三)API学习之常用common_msgs(上). 二.sensor_msgs 1.sensor_msgs / BatteryState.msg #电源状态 uint8 P ...

  8. CRC校验算法详解

    CRC(Cyclic Redundancy Check)循环冗余校验是常用的数据校验方法,讲CRC算法的文章很多,之所以还要写这篇,是想换一个方法介绍CRC算法,希望能让大家更容易理解CRC算法. 先 ...

  9. Intellij IDEA中配置TFS

    TFS是微软推出的一款研发过程管理利器,C#阵营的VS里做了默认集成,但是对于Java阵营的Intellij IDEA,需要安装插件并进行相应配置才能使用: 1.打开配置 2.搜索并安装插件 3.配置 ...

  10. 009-PHP循环输出数组成员

    <?php $Cities[] = "<B>北京</B>"; //等同于$Cities[0] = "北京" $Cities[] = ...