.NET中lock的使用方法及注意事项[转]
标准-->ms官方:
http://msdn.microsoft.com/zh-cn/library/c5kehkcz(v=vs.90).aspx
A、为什么不要 "lock(this)" ? lock object 并是readonly
http://blog.csdn.net/gulijiang2008/article/details/7257422
B、转自:http://daimajishu.iteye.com/blog/1079107
lock就是把一段代码定义为临界区,所谓临界区就是同一时刻只能有一个线程来操作临界区的代码,当一个线程位于代码的临界区时,另一个线程不能进入临界区,如果试图进入临界区,则只能一直等待(即被阻止),直到已经进入临界区的线程访问完毕,并释放锁旗标。
其基本使用方式如下:
- class Test
- {
- //定义一个私有成员变量,用于Lock
- private static object lockobj = new object();
- void DoSomething()
- {
- lock (lockobj)
- {
- //需要锁定的代码块
- }
- }
- }
最经典的例子,莫过于模拟银行5个窗口取钱操作的例子了,5个窗口是5个线程,都要取钱,但是同一刻只能有一个窗口可以进行真正的取钱操作(钱数的数值计算,剩余多少等这些代码必须定义为临界区),其他只有等待,其代码如下:
- using System;
- using System.Threading;
- class Account
- {
- int balance;
- Random r = new Random();
- public Account(int initial)
- {
- balance = initial;
- }
- int Withdraw(int amount)
- {
- // This condition will never be true unless the lock statement
- // is commented out:
- if (balance < 0)
- {
- throw new Exception("Negative Balance");
- }
- // Comment out the next line to see the effect of leaving out
- // the lock keyword:
- lock (this)
- {
- if (balance >= amount)
- {
- Console.WriteLine("提款之前余额(Balance before Withdrawal): " + balance);
- Console.WriteLine("提款数量(Amount to Withdraw) : -" + amount);
- balance = balance - amount;
- Console.WriteLine("提款之后余额(Balance after Withdrawal) : " + balance);
- Console.WriteLine();
- return amount;
- }
- else
- {
- return 0; // transaction rejected
- }
- }
- }
- public void DoTransactions()
- {
- //模拟100个人来提款,每次提[1-10)元
- for (int i = 0; i < 100; i++)
- {
- Withdraw(r.Next(1, 10));
- }
- }
- }
- class Test
- {
- public static void Main()
- {
- Thread[] threads = new Thread[5];
- //总额为100元
- Account acc = new Account (100);
- //定义并初始化5个线程,模拟银行的5个窗口
- for (int i = 0; i < 5; i++)
- {
- Thread t = new Thread(new ThreadStart(acc.DoTransactions));
- threads[i] = t;
- }
- //启动5个线程,模拟银行的5个窗口开始工作
- for (int i = 0; i < 5; i++)
- {
- Console.WriteLine("threads[{0}].Start()", i);
- threads[i].Start();
- }
- }
- }
运算结果:
threads[0].Start()
threads[1].Start()
提款之前余额(Balance before Withdrawal): 100
提款数量(Amount to Withdraw) : -4
提款之后余额(Balance after Withdrawal) : 96
提款之前余额(Balance before Withdrawal): 96
提款数量(Amount to Withdraw) : -5
提款之后余额(Balance after Withdrawal) : 91
提款之前余额(Balance before Withdrawal): 91
提款数量(Amount to Withdraw) : -4
提款之后余额(Balance after Withdrawal) : 87
提款之前余额(Balance before Withdrawal): 87
提款数量(Amount to Withdraw) : -9
提款之后余额(Balance after Withdrawal) : 78
提款之前余额(Balance before Withdrawal): 78
threads[2].Start()
提款数量(Amount to Withdraw) : -8
提款之后余额(Balance after Withdrawal) : 70
提款之前余额(Balance before Withdrawal): 70
提款数量(Amount to Withdraw) : -6
提款之后余额(Balance after Withdrawal) : 64
提款之前余额(Balance before Withdrawal): 64
提款数量(Amount to Withdraw) : -1
提款之后余额(Balance after Withdrawal) : 63
提款之前余额(Balance before Withdrawal): 63
提款数量(Amount to Withdraw) : -4
提款之后余额(Balance after Withdrawal) : 59
提款之前余额(Balance before Withdrawal): 59
提款数量(Amount to Withdraw) : -2
提款之后余额(Balance after Withdrawal) : 57
提款之前余额(Balance before Withdrawal): 57
提款数量(Amount to Withdraw) : -1
提款之后余额(Balance after Withdrawal) : 56
提款之前余额(Balance before Withdrawal): 56
提款数量(Amount to Withdraw) : -9
提款之后余额(Balance after Withdrawal) : 47
提款之前余额(Balance before Withdrawal): 47
提款数量(Amount to Withdraw) : -7
提款之后余额(Balance after Withdrawal) : 40
提款之前余额(Balance before Withdrawal): 40
提款数量(Amount to Withdraw) : -5
提款之后余额(Balance after Withdrawal) : 35
提款之前余额(Balance before Withdrawal): 35
提款数量(Amount to Withdraw) : -1
提款之后余额(Balance after Withdrawal) : 34
提款之前余额(Balance before Withdrawal): 34
提款数量(Amount to Withdraw) : -1
提款之后余额(Balance after Withdrawal) : 33
提款之前余额(Balance before Withdrawal): 33
提款数量(Amount to Withdraw) : -2
提款之后余额(Balance after Withdrawal) : 31
提款之前余额(Balance before Withdrawal): 31
提款数量(Amount to Withdraw) : -2
提款之后余额(Balance after Withdrawal) : 29
提款之前余额(Balance before Withdrawal): 29
提款数量(Amount to Withdraw) : -3
提款之后余额(Balance after Withdrawal) : 26
提款之前余额(Balance before Withdrawal): 26
提款数量(Amount to Withdraw) : -3
提款之后余额(Balance after Withdrawal) : 23
提款之前余额(Balance before Withdrawal): 23
提款数量(Amount to Withdraw) : -8
提款之后余额(Balance after Withdrawal) : 15
提款之前余额(Balance before Withdrawal): 15
提款数量(Amount to Withdraw) : -6
提款之后余额(Balance after Withdrawal) : 9
提款之前余额(Balance before Withdrawal): 9
提款数量(Amount to Withdraw) : -9
提款之后余额(Balance after Withdrawal) : 0
threads[3].Start()
threads[4].Start()
请按任意键继续. . .
发现窗口1 threads[1].Start()和窗口2 threads[2].Start()先进行取钱,等窗口3 threads[3].Start()和窗口4 threads[4].Start()去取钱的时候,已经没钱了。
使用lock需要注意的地方:
1.lock不能锁定空值
某一对象可以指向Null,但Null是不需要被释放的。(请参考:认识全面的null)
2.lock不能锁定string类型,虽然它也是引用类型的。因为字符串类型被CLR“暂留”
这意味着整个程序中任何给定字符串都只有一个实例,就是这同一个对象表示了所有运行的应用程序域的所有线程中的该文本。因此,只要在应用程序进程中的任何位置处具有相同内容的字符串上放置了锁,就将锁定应用程序中该字符串的所有实例。因此,最好锁定不会被暂留的私有或受保护成员。
3.lock锁定的对象是一个程序块的内存边界
4.值类型不能被lock,因为前文标红字的“对象被释放”,值类型不是引用类型的
5.lock就避免锁定public 类型或不受程序控制的对象。
例如,如果该实例可以被公开访问,则 lock(this) 可能会有问题,因为不受控制的代码也可能会锁定该对象。这可能导致死锁,即两个或更多个线程等待释放同一对象。出于同样的原因,锁定公共数据类型(相比于对象)也可能导致问题。
使用lock(this)的时候,类的成员变量的值可能会被不在临界区的方法改值了
如下面的测试:
- using System.Threading;
- using System;
- public class ThreadTest
- {
- private int i = 0;
- public void Test()
- {
- Thread t1 = new Thread(Thread1);
- Thread t2 = new Thread(Thread2);
- t1.Start();
- t2.Start();
- }
- public void Thread1()
- {
- lock (this)
- {
- Console.WriteLine(this.i);
- Thread.Sleep(1000);
- Console.WriteLine(this.i);
- }
- }
- public void Thread2()
- {
- Thread.Sleep(500);
- this.i = 1;
- Console.WriteLine("Change the value in locking");
- }
- }
- public class ThreadTest2
- {
- private int i = 0;
- public void Test()
- {
- Thread t1 = new Thread(Thread1);
- Thread t2 = new Thread(Thread2);
- t1.Start();
- t2.Start();
- }
- public void Thread1()
- {
- lock (this)
- {
- Console.WriteLine(this.i);
- Thread.Sleep(1000);
- Console.WriteLine(this.i);
- }
- }
- public void Thread2()
- {
- lock (this)
- {
- Thread.Sleep(500);
- this.i = 1;
- Console.WriteLine("Can't change the value in locking");
- }
- }
- }
- public class ThreadMain
- {
- public static void Main()
- {
- //ThreadTest b = new ThreadTest();
- //Thread t = new Thread(new ThreadStart(b.Test));
- //t.Start();
- ThreadTest2 b2 = new ThreadTest2();
- Thread t2 = new Thread(new ThreadStart(b2.Test));
- t2.Start();
- }
- }
测试ThreadTest的运行结果:
0
Change the value in locking
1
请按任意键继续. . .
测试ThreadTest2的运行结果:
0
0
Can't change the value in locking
请按任意键继续. . .
发现第一个测试里成员变量i被改值了。
本想在案例一中lock住this对象,让其他的线程不能操作,可是事情不是像我们想象的那样lock(this)是lock this的意思.this中的属性依然能够被别的线程改变.那我们lock住的是什么?是代码段,是lock后面大括号中代码段,这段代码让多个人执行不不被允许的.那返回头来在看lock(this),this是什么意思呢?可以说this知识这段代码域的标志,看看案例二中Thread2.Thread2就明白了,Thread2中的lock需要等到Thread1种lock释放后才开始运行,释放之前一直处于等待状态,这就是标志的表现.
好吧,让我们来了解一下,lock这段代码是怎么运行的.lock语句根本使用的就是Monitor.Enter和Monitor.Exit,也就是说lock(this)时执行Monitor.Enter(this),大括号结束时执行Monitor.Exit(this).他的意义在于什么呢,对于任何一个对象来说,他在内存中的第一部分放置的是所有方法的地址,第二部分放着一个索引,他指向CLR中的SyncBlock Cache区域中的一个SyncBlock.什么意思呢?就是说,当你执行Monitor.Enter(Object)时,如果object的索引值为负数,就从SyncBlock Cache中选区一个SyncBlock,将其地址放在object的索引中。这样就完成了以object为标志的锁定,其他的线程想再次进行Monitor.Enter(object)操作,将获得object为正数的索引,然后就等待。直到索引变为负数,即线程使用Monitor.Exit(object)将索引变为负数。
如果明白了Monitor.Enter的原理,lock当然不再话下.当然lock后括号里面的值不是说把整个对象锁住,而是对他的一个值进行了修改,使别的lock不能锁住他,这才是lock(object)的真面目.
但在实际使用中Monitor还是不推荐,还是lock好的,Monitor需要加上很多try catch才能保证安全性,但lock却帮我们做了,而且lock看起来更优雅.
在静态方法中如何使用lock呢,由于我们没有this可用,所以我们使用typeof(this)好了,Type也有相应的方法地址和索引,所以他也是可以来当作lock的标志的.
但微软不提倡是用public的object或者typeof()或者字符串这样的标志就是因为,如果你的public object在其他的线程中被null并被垃圾收集了,将发生不可预期的错误.
参考:
.NET中lock的使用方法及注意事项[转]的更多相关文章
- .NET中lock的使用方法及注意事项
lock就是把一段代码定义为临界区,所谓临界区就是同一时刻只能有一个线程来操作临界区的代码,当一个线程位于代码的临界区时,另一个线程不能进入临界区,如果试图进入临界区,则只能一直等待(即被阻止),直到 ...
- PHP中$_FILES的使用方法及注意事项说明
$_FILES:经由 HTTP POST 文件上传而提交至脚本的变量,类似于旧数组$HTTP_POST_FILES 数组(依然有效,但反对使用)详细信息可参阅 POST方法上传 $_FILES数组内容 ...
- Spring中使用Ehcache的方法和注意事项
如何调用方法数据增加缓存 @Cacheable(value="MY_CACHE", key="'cache_business_' + #business_id" ...
- jQuery 中get 和post 方法传值注意事项
用 jQuery 的都知道,jQuery 的 get 和 post 方法有三个参数:地址,数据 和回调函数,但我们知道地址也可以跟随数据的(形如:get_data.php?v1=1&v2=2) ...
- iPhone SDK中多线程的使用方法以及注意事项
多线程iphonethreadapplication编程嵌入式 然现在大部分PC应用程序都支持多线程/多任务的开发方式,但是在iPhone上,Apple并不推荐使用多线程的编程方式.但是多线程编程毕竟 ...
- js中数组的定义方法及注意事项(转)
1.数组的创建 var name= new Array(); //创建一个数组 name[0]="zhangsan"; //给数组赋值 name[1]="lisi&q ...
- js中拼接HTML方式方法及注意事项
博主原创:未经博主允许,不得转载 在前端应用中,经常需要在js中动态拼接HTML页面,比如应用ajax进行局部刷新的时候,就需要在js中拼接HTML页面. 主要规则是将HTML页面的标签拼接为标签字符 ...
- AntiXSS v4.0中Sanitizer.GetSafeHtmlFragment等方法将部分汉字编码为乱码的解决方案
AntiXSS v4.0中Sanitizer.GetSafeHtmlFragment等方法将部分汉字编码为乱码的解决方案 以下代码为asp.net环境下,c#语言编写的解决方案.数据用Dictiona ...
- c#初学-多线程中lock用法的经典实例
本文转载自:http://www.cnblogs.com/promise-7/articles/2354077.html 一.Lock定义 lock 关键字可以用来确保代码块完成运行,而不会被 ...
随机推荐
- 35个Redis面试题
1.什么是redis? Redis 是一个基于内存的高性能key-value数据库. 2.Reids的特点 Redis本质上是一个Key-Value类型的内存数据库,很像memcached,整个数据库 ...
- 三、C++ const分析
1.C语言中的const: const修饰的变量是只读的,本质还是变量 const修饰的局部变量在栈上分配空间 const修饰的全局变量在只读存储区分配空间 const只在编译期有用,在运行期无效 c ...
- Ubuntu14.04环境下Qt5.5以上版本无法输入中文的解决教程
1.前言 由于Qt5.4之后对之前的Qt5版本不再二进制兼容,所以网上很多简单的旧的办法已经失效了,所以本教程的办法是重新编译fcitx-qt5,生成最新的libfcitxplatforminputc ...
- 【HIHOCODER 1478】 水陆距离(BFS)
描述 给定一个N x M的01矩阵,其中1表示陆地,0表示水域.对于每一个位置,求出它距离最近的水域的距离是多少. 矩阵中每个位置与它上下左右相邻的格子距离为1. 输入 第一行包含两个整数,N和M. ...
- 【转】如何在命令行脚本中启动带参数的Windows服务
我们有一个自己编写的Windows服务,我们希望该服务在启动时可以根据用户输入的参数实现不同的功能. 要实现这样的需求并不是很难,下面这个例子我用来示范如何编写该服务 using System; us ...
- httpclient调用webservice接口的方法实例
这几天在写webservice接口,其他的调用方式要生成客户端代码,比较麻烦,不够灵活,今天学习了一下httpclient调用ws的方式,感觉很实用,话不多说,上代码 http://testhcm.y ...
- Git x SVN 当前工作流程
git-svn 当前工作流程 @ixenos 2018-12-27 21:37:47 前言:用惯了git,再用svn简直反人类,所以……还是用git-svn过渡一下 (由于远程还没有dev,直接坑爹地 ...
- centos 7 下vnc弹出窗口太小解决方法
使用以下参数启动 : vncserver :2 -geometry 800x600
- [luoguP3606] [USACO17JAN]Building a Tall Barn建谷仓(贪心 + 线段树)
传送门 把线段都读进来然后排序,先按右端点为第一关键字从小到大排序,后按左端点为第二关键字从小到大排序. 注意不能先按左端点后按右端点排序,否则会出现大包小的情况,如下: —————— ——— — ...
- [HDU4417]Super Mario(主席树+离散化)
传送门 又是一道主席树模板题,注意数组从0开始,还有主席树耗费空间很大,数组开大点,之前开小了莫名其妙TLE.QAQ ——代码 #include <cstdio> #include < ...