当前位置: 首页 > news >正文

[C#基础]说说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方法,可以这样猜测“直到该对象被释放”,”该对象“应该是指锁的对象,对象释放了或者对象改变了,其他的线程才可以进入代码临界区(是不是可以这样来理解?)。

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

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

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

private static object objlock = new object();
lock (objlock )
{
    //要执行的代码逻辑
}

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

lock(this)

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

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

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

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

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

输出结果

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

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

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

输出的结果为

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

lock(private obj)

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

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

输出的结果

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

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

也就有了类似下面的代码

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的意义。

参考文章

http://www.cnblogs.com/jintianhu/archive/2010/11/19/1881494.html

博客地址:http://www.cnblogs.com/wolf-sun/
博客版权:本文以学习、研究和分享为主,欢迎转载,但必须在文章页面明显位置给出原文连接。
如果文中有不妥或者错误的地方还望高手的你指出,以免误人子弟。如果觉得本文对你有所帮助不如【推荐】一下!如果你有更好的建议,不如留言一起讨论,共同进步!
再次感谢您耐心的读完本篇文章。http://www.cnblogs.com/wolf-sun/p/4209521.html

相关文章:

  • Shell学习笔记---重定向输入、输出(原创)
  • 堆的一些简单应用
  • dtrace4linux_Example
  • jQuery.extend 函数详解
  • Shiro安全框架入门篇(登录验证实例详解与源码)
  • goldengate一些参数整理
  • HDU1161 Eddy's mistakes
  • Unity3d标签管理类-利用脚本控制标签,提升工作效率
  • putty如何设置密钥登陆
  • 程序员该如何有效的找工作?
  • Android 认识Activity 生命周期
  • C#基础内容学习笔记(一)
  • Open CV缩放图像
  • 程序员之路:以Android证道
  • 地域划分
  • [case10]使用RSQL实现端到端的动态查询
  • [分享]iOS开发-关于在xcode中引用文件夹右边出现问号的解决办法
  • javascript 总结(常用工具类的封装)
  • leetcode46 Permutation 排列组合
  • LintCode 31. partitionArray 数组划分
  • maya建模与骨骼动画快速实现人工鱼
  • nodejs调试方法
  • OSS Web直传 (文件图片)
  • passportjs 源码分析
  • tab.js分享及浏览器兼容性问题汇总
  • ubuntu 下nginx安装 并支持https协议
  • 面试题:给你个id,去拿到name,多叉树遍历
  • 听说你叫Java(二)–Servlet请求
  • 7行Python代码的人脸识别
  • Android开发者必备:推荐一款助力开发的开源APP
  • 回归生活:清理微信公众号
  • ​queue --- 一个同步的队列类​
  • ### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTr
  • #Z2294. 打印树的直径
  • (java)关于Thread的挂起和恢复
  • (rabbitmq的高级特性)消息可靠性
  • (附源码)springboot建达集团公司平台 毕业设计 141538
  • (九)One-Wire总线-DS18B20
  • (转) ns2/nam与nam实现相关的文件
  • (转)使用VMware vSphere标准交换机设置网络连接
  • ../depcomp: line 571: exec: g++: not found
  • .net core Swagger 过滤部分Api
  • .NET 使用 ILRepack 合并多个程序集(替代 ILMerge),避免引入额外的依赖
  • .NET/MSBuild 中的发布路径在哪里呢?如何在扩展编译的时候修改发布路径中的文件呢?
  • .Net调用Java编写的WebServices返回值为Null的解决方法(SoapUI工具测试有返回值)
  • .Net中间语言BeforeFieldInit
  • @RequestMapping 的作用是什么?
  • [ Linux 长征路第二篇] 基本指令head,tail,date,cal,find,grep,zip,tar,bc,unname
  • [20150321]索引空块的问题.txt
  • [BUUCTF 2018]Online Tool(特详解)
  • [BZOJ1877][SDOI2009]晨跑[最大流+费用流]
  • [CISCN2019 华北赛区 Day1 Web2]ikun
  • [CISCN2019 华东北赛区]Web2
  • [CTF]2022美团CTF WEB WP
  • [CUDA 学习笔记] CUDA kernel 的 grid_size 和 block_size 选择