.net lock的使用
内容参考自:http://daimajishu.iteye.com/blog/1079107
四.lock应避免锁定public 类型或不受程序控制的对象,举例
lock就是把一段代码定义为临界区,所谓临界区就是同一时刻只能有一个线程来操作临界区的代码,当一个线程位于代码的临界区时,另一个线程不能进入临界区,如果试图进入临界区,则只能一直等待(即被阻止),直到已经进入临界区的线程访问完毕,并释放锁旗标。
其基本使用方式如下:
class Test
{
//定义一个私有成员变量,用于Lock的锁定标志
private static object lockobj = new object();
void DoSomething()
{
lock (lockobj)
{
//需要锁定的代码块
}
}
}
最经典的例子,莫过于模拟银行5个窗口取钱操作的例子了,5个窗口是5个线程,都要取钱,但是同一刻只能有一个窗口可以进行真正的取钱操作(钱数的数值计算,剩余多少等这些代码必须定义为临界区),其他只有等待,其代码如下:
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 < )
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("提款窗口: " + Thread.CurrentThread.Name);
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 ; // transaction rejected
}
} public void DoTransactions()
{
//模拟100个人来提款,每次提1-30元
for (int i = ; i < ; i++)
Withdraw(r.Next(, ));
}
}
class Test
{
public static void MainXXX()
{
Thread[] threads = new Thread[]; //总额为100元
Account acc = new Account(); //定义并初始化5个线程,模拟银行的5个窗口
for (int i = ; i < ; i++)
{
Thread t = new Thread(new ThreadStart(acc.DoTransactions)) { Name = i + "号" };
threads[i] = t;
} //启动5个线程,模拟银行的5个窗口开始工作
for (int i = ; i < ; i++)
{
Console.WriteLine("threads[{0}].Start()", i);
threads[i].Start();
}
}
}
运算结果:
threads[0].Start()
threads[1].Start()
threads[2].Start()
提款窗口: 0号
提款之前余额(Balance before Withdrawal): 100
threads[3].Start()
提款数量(Amount to Withdraw) : -18
提款之后余额(Balance after Withdrawal) : 82
提款窗口: 1号
提款之前余额(Balance before Withdrawal): 82
提款数量(Amount to Withdraw) : -9
提款之后余额(Balance after Withdrawal) : 73
提款窗口: 1号
提款之前余额(Balance before Withdrawal): 73
提款数量(Amount to Withdraw) : -4
提款之后余额(Balance after Withdrawal) : 69
提款窗口: 1号
提款之前余额(Balance before Withdrawal): 69
提款数量(Amount to Withdraw) : -4
提款之后余额(Balance after Withdrawal) : 65
提款窗口: 0号
threads[4].Start()
提款之前余额(Balance before Withdrawal): 65
提款数量(Amount to Withdraw) : -12
提款之后余额(Balance after Withdrawal) : 53
提款窗口: 2号
提款之前余额(Balance before Withdrawal): 53
提款数量(Amount to Withdraw) : -26
提款之后余额(Balance after Withdrawal) : 27
提款窗口: 3号
提款之前余额(Balance before Withdrawal): 27
提款数量(Amount to Withdraw) : -27
提款之后余额(Balance after Withdrawal) : 0
使用lock需要注意的地方:
1. lock不能锁定空值
某一对象可以指向Null,但Null是不需要被释放的。(请参考:认识全面的null)
2. lock不能锁定string类型,虽然它也是引用类型的。因为字符串类型被CLR“暂留”
这意味着整个程序中任何给定字符串都只有一个实例,就是这同一个对象表示了所有运行的应用程序域的所有线程中的该文本。因此,只要在应用程序进程中的任何位置处具有相同内容的字符串上放置了锁,就将锁定应用程序中该字符串的所有实例。因此,最好锁定不会被暂留的私有或受保护成员。
3. lock锁定的对象是一个程序块的内存边界
4. 值类型不能被lock,因为前文标红字的“对象被释放”,值类型不是引用类型的
5. lock就避免锁定public 类型或不受程序控制的对象。
例如,如果该实例可以被公开访问,则 lock(this) 可能会有问题,因为不受控制的代码也可能会锁定该对象。这可能导致死锁,即两个或更多个线程等待释放同一对象。出于同样的原因,锁定公共数据类型(相比于对象)也可能导致问题。
使用lock(this)的时候,类的成员变量的值可能会被不在临界区的方法改值了
如下面的测试:
class ThreadTest
{
private int i = ;
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();
Console.WriteLine(this.i);
}
}
public void Thread2()
{
Thread.Sleep();
this.i = ;
Console.WriteLine("Change the value in locking");
}
}
public class ThreadTest2
{
private int i = ;
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();
Console.WriteLine(this.i);
}
}
public void Thread2()
{
lock (this)
{
Thread.Sleep();
this.i = ;
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并被垃圾收集了,将发生不可预期的错误.
15.10.24补充
关于锁的理解不够透彻,最近又做了一轮测试。测试结果表明:
同一个锁,只要当前线程已经拥有此锁,不管是在锁对应的哪一个代码段,都可以畅通无阻。
测试代码一:
public class LockTest
{
int idx = ;
public void ChangeIdx()
{
lock (this)
{
Console.WriteLine(idx);
if (idx < )
{
idx++;
ChangeIdx();
}
}
}
}
测试代码二:
public class ThreadTest2
{
private int i = ;
public void Test()
{
Thread1();
}
public void Thread1()
{
lock (this)
{
Console.WriteLine(this.i);
Thread.Sleep();
Console.WriteLine(this.i);
Thread2();
}
}
public void Thread2()
{
lock (this)
{
Thread.Sleep();
this.i = ;
Console.WriteLine("Can't change the value in locking");
}
}
}
代码一的目的是测试同代码段多次Enter的情况;代码二的目的则是不同代码段,段一还未Exit前,段二Enter。结果,两个都执行的很流畅。
MSDN参考文档:https://msdn.microsoft.com/zh-cn/library/c5kehkcz.aspx
.net lock的使用的更多相关文章
- C#各种同步方法 lock, Monitor,Mutex, Semaphore, Interlocked, ReaderWriterLock,AutoResetEvent, ManualResetEvent
看下组织结构: System.Object System.MarshalByRefObject System.Threading.WaitHandle System.Threading.Mutex S ...
- 多线程同步工具——Lock
本文原创,转载请注明出处. 参考文章: <"JUC锁"03之 公平锁(一)> <"JUC锁"03之 公平锁(二)> 锁分独占锁与共享锁, ...
- java 线程 Lock 锁使用Condition实现线程的等待(await)与通知(signal)
一.Condition 类 在前面我们学习与synchronized锁配合的线程等待(Object.wait)与线程通知(Object.notify),那么对于JDK1.5 的 java.util.c ...
- InnoDB:Lock & Transaction
InnoDB 是一个支持事务的Engine,要保证事务ACID,必然会用到Lock.就像在Java编程一下,要保证数据的线程安全性,必然会用到Lock.了解Lock,Transaction可以帮助sq ...
- 使用四元数解决万向节锁(Gimbal Lock)问题
问题 使用四元数可以解决万向节锁的问题,但是我在实际使用中出现问题:我设计了一个程序,显示一个三维物体,用户可以输入绕zyx三个轴进行旋转的指令,物体进行相应的转动. 由于用户输入的是绕三个轴旋转的角 ...
- 万向节锁(Gimbal Lock)的理解
[TOC] 结论 我直接抛出结论: Gimbal Lock 产生的原因不是欧拉角也不是旋转顺序,而是我們的思维方式和程序的执行逻辑没有对应,也就是说是我们的观念导致这个情况的发生. 他人解释 首先我们 ...
- 在多线程编程中lock(string){...}隐藏的机关
常见误用场景:在订单支付环节中,为了防止用户不小心多次点击支付按钮而导致的订单重复支付问题,我们用 lock(订单号) 来保证对该订单的操作同时只允许一个线程执行. 这样的想法很好,至少比 lock( ...
- 谈谈 Lock
上来先看MSDN关于lock的叙述: lock 关键字将语句块标记为临界区,方法是获取给定对象的互斥锁,执行语句,然后释放该锁. 下面的示例包含一个 lock 语句. lock 关键字可确保当一 ...
- LOCK TABLES和UNLOCK TABLES与Transactions的交互
LOCK TABLES对事务不安全,并且在试图锁定表之前隐式提交任何活动事务. UNLOCK TABLES只有在LOCK TABLES已经获取到表锁时,会隐式提交任何活动事务.对于下面的一组语句,UN ...
- SQL 性能调优中可参考的几类Lock Wait
在我们的系统出现性能问题时,往往避不开调查各种类型 Lock Wait,如Row Lock Wait.Page Lock Wait.Page IO Latch Wait等.从中找出可能的异常等待,为性 ...
随机推荐
- js 万恶之源 是否滚动到底部?
let scrollHandle = (el) => { // 如果已经滚到底部了 if (el.scrollHeight - el.scrollTop === el.clientHeight) ...
- fcitx五笔的安装[zz]
Fcitx──小企鹅输入法:Free Chinese Input Toy for X是国产软件的精品,是一个以GPL方式发布的.基于XIM的简体中文输入法集合(原为G五笔),包括五笔.五笔拼音.二笔. ...
- Maven报错 解决方案。ERROR: No goals have been specified for this build. You must specify a valid lifecycle phase or a goal in the format <plugin-prefix>:<goal> or <plugin-group-id>:<plugin-artifact-id
报错: [ERROR] No goals have been specified for this build. You must specify a valid lifecycle phase or ...
- NSString和NSMutablestring,copy和strong(转载)
1.http://www.cocoachina.com/ios/20150512/11805.html 2.http://blog.csdn.net/winzlee/article/details/5 ...
- Atitit nodejs5 nodejs6 nodejs 7.2.1 新特性attialx总结
Atitit nodejs5 nodejs6 nodejs 7.2.1 新特性attialx总结 1.1. Node.js 4.0.0 已经发布了 .这是和 io.js 合并之后的首个稳定版本,它 ...
- [svc]ssh+gg二步认证
1,安装依赖 yum install python-pip -y pip install docutils yum install gcc python-devel subversion pam pa ...
- LeetCode263——Ugly Number
Write a program to check whether a given number is an ugly number. Ugly numbers are positive numbers ...
- Flink 中的kafka何时commit?
https://ci.apache.org/projects/flink/flink-docs-release-1.6/internals/stream_checkpointing.html @Ove ...
- 把虚拟教练带回家,「EuMotus」想用AI实现高精度运动反馈系统
https://36kr.com/p/5089139.html 无需穿戴设备,只需一个红外摄像头和+已安装好EuMotus专利软件的手提电脑 由政府主导的高达2200亿美金的健身与运动支出,15%的健 ...
- JAVA中转义字符
JAVA中转义字符 2010年08月11日 星期三 上午 12:22 JAVA中转义字符: 1.八进制转义序列:\ + 1到3位5数字:范围'\000'~'\377' \0:空字符 2.U ...