.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等.从中找出可能的异常等待,为性 ...
随机推荐
- Android之旅-Intent与Intent Filter[上]
Intent代表了Android应用的启动“意图”,Android应用将会根据Intent来启动指定组件,至于到底启动哪个组件,取决于Intent的各个属性. 一.显式的Intent 明确指定了要启动 ...
- Hibernate 连接访问多个数据库(含访问不同数据库的相同表)(转)
利用hibernate访问不同数据库中的不同表或不同数据库中的相同表. 本人在开发过程中的解决方案,希望大家交流.一般用myEclipse工具会自动生成Hibernate的相关文件,大致有下面几类: ...
- Intellij dea 注释行如何自动缩进?
Intellij dea 注释行如何自动缩进? 进入 Settings -> Code Style -> Java ,在右边选择 “Code Generation” Tab,然后找到 ...
- [LeetCode] Contains Duplicate & Contains Duplicate II
Contains Duplicate Given an array of integers, find if the array contains any duplicates. Your funct ...
- Bootstrap FileInput中文API整理
这段时间做项目用到bootstrap fileinput插件上传文件,在用的过程中,网上能查到的api都不是很全,所以想着整理一份比较详细的文档,方便自己今后使用,也希望能给大家带来帮助,如有错误,希 ...
- fork failed.: Cannot allocate memory
在做压力测试时候: [root@666 ok]# webbench -c 5000 -t30 http://10.100.0.61/ Webbench - Simple Web Benchmark 1 ...
- https://jzh.12333sh.gov.cn/jzh/
https://jzh.12333sh.gov.cn/jzh/ https://superuser.com/questions/171917/force-a-program-to-run-withou ...
- python __name__ = '__main__' 的作用
很多新手刚开始学习python的时候经常会看到python 中__name__ = \'__main__\' 这样的代码,可能很多新手一开始学习的时候都比较疑惑,python 中__name__ = ...
- tensorflow笔记2:TensorBoard
Tensorboard中的参数 Summary:所有需要在TensorBoard上展示的统计结果. tf.name_scope():为Graph中的Tensor添加层级,TensorBoard会按照代 ...
- SVN文件加锁
原文:SVN与TortoiseSVN实战:文件加锁详解 加锁与解锁的操作对于项目中的二进制文件,如图片.声音.动态库等不可合并文件是非常有用的,可以让这些文件防止产生恼人的冲突,但TortoiseSV ...