内容参考自: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的使用的更多相关文章

  1. C#各种同步方法 lock, Monitor,Mutex, Semaphore, Interlocked, ReaderWriterLock,AutoResetEvent, ManualResetEvent

    看下组织结构: System.Object System.MarshalByRefObject System.Threading.WaitHandle System.Threading.Mutex S ...

  2. 多线程同步工具——Lock

    本文原创,转载请注明出处. 参考文章: <"JUC锁"03之 公平锁(一)> <"JUC锁"03之 公平锁(二)> 锁分独占锁与共享锁, ...

  3. java 线程 Lock 锁使用Condition实现线程的等待(await)与通知(signal)

    一.Condition 类 在前面我们学习与synchronized锁配合的线程等待(Object.wait)与线程通知(Object.notify),那么对于JDK1.5 的 java.util.c ...

  4. InnoDB:Lock & Transaction

    InnoDB 是一个支持事务的Engine,要保证事务ACID,必然会用到Lock.就像在Java编程一下,要保证数据的线程安全性,必然会用到Lock.了解Lock,Transaction可以帮助sq ...

  5. 使用四元数解决万向节锁(Gimbal Lock)问题

    问题 使用四元数可以解决万向节锁的问题,但是我在实际使用中出现问题:我设计了一个程序,显示一个三维物体,用户可以输入绕zyx三个轴进行旋转的指令,物体进行相应的转动. 由于用户输入的是绕三个轴旋转的角 ...

  6. 万向节锁(Gimbal Lock)的理解

    [TOC] 结论 我直接抛出结论: Gimbal Lock 产生的原因不是欧拉角也不是旋转顺序,而是我們的思维方式和程序的执行逻辑没有对应,也就是说是我们的观念导致这个情况的发生. 他人解释 首先我们 ...

  7. 在多线程编程中lock(string){...}隐藏的机关

    常见误用场景:在订单支付环节中,为了防止用户不小心多次点击支付按钮而导致的订单重复支付问题,我们用 lock(订单号) 来保证对该订单的操作同时只允许一个线程执行. 这样的想法很好,至少比 lock( ...

  8. 谈谈 Lock

    上来先看MSDN关于lock的叙述: lock  关键字将语句块标记为临界区,方法是获取给定对象的互斥锁,执行语句,然后释放该锁.  下面的示例包含一个 lock 语句. lock  关键字可确保当一 ...

  9. LOCK TABLES和UNLOCK TABLES与Transactions的交互

    LOCK TABLES对事务不安全,并且在试图锁定表之前隐式提交任何活动事务. UNLOCK TABLES只有在LOCK TABLES已经获取到表锁时,会隐式提交任何活动事务.对于下面的一组语句,UN ...

  10. SQL 性能调优中可参考的几类Lock Wait

    在我们的系统出现性能问题时,往往避不开调查各种类型 Lock Wait,如Row Lock Wait.Page Lock Wait.Page IO Latch Wait等.从中找出可能的异常等待,为性 ...

随机推荐

  1. Linux查看磁盘占用率及文件大小

    查看磁盘占用率: 在 df 命令中使用-h选项,以人类易读的格式输出(例如,5K,500M 及 5G) linux中df命令的功能是用来检查linux服务器的文件系统的磁盘空间占用情况.可以利用该命令 ...

  2. 使用 NodeJS + Express 從 GET/POST Request 取值 -摘自网络

    過去無論哪一種網站應用程式的開發語言,初學者教學中第一次會提到的起手式,八九不離十就是 GET/POST Request 的取值.但是,在 Node.js + Express 的世界中,彷彿人人是高手 ...

  3. OpenXml SDK 2.0 创建Word文档 添加页、段落、页眉和页脚

    using (WordprocessingDocument objWordDocument = WordprocessingDocument.Create(@"C:\********.doc ...

  4. haproxy 日志配置

    haproxy日志配置 haproxy在默认情况不会记录日志,除了在haproxy.conf中的global段指定日志的输出外,还需要配置系统日志的配置文件.下面以centos6.4为例,haprox ...

  5. SpringMVC 封装返回结果对象

    /*** *请求返回的最外层对象 **/ public class Result<T>{ /*错误码*/ private Integer code; /*提示信息*/ private St ...

  6. jmeter http协议---cookie处理

    使用jmeter测试的http接口的时候,经常遇到需要cookie做免登录等作用的情况,jmeter如何在测试http接口的时候加载所需cookie呢?主要分两步 一. 收集对应站点的cookie 1 ...

  7. iOS数据库离线缓存思路和网络层封装

    一直想总结一下关于iOS的离线数据缓存的方面的问题,然后近期也简单的对AFN进行了再次封装.全部想把这两个结合起来写一下.数据展示型的页面做离线缓存能够有更好的用户体验,用户在离线环境下仍然能够获取一 ...

  8. Android开发:SharedPreferences 存储数据、获取数据

    Android开发:SharedPreferences 存储数据.获取数据 email:chentravelling@163.com 开发环境:win7 64位,Android Studio. 关于S ...

  9. jQuery页面引导插件 jquery-pagewalkthrough

    http://jwarby.github.io/jquery-pagewalkthrough/ 源码 https://github.com/jwarby/jquery-pagewalkthrough

  10. 菜鸟学SSH(十四)——Spring容器AOP的实现原理——动态代理

    之前写了一篇关于IOC的博客——<Spring容器IOC解析及简单实现>,今天再来聊聊AOP.大家都知道Spring的两大特性是IOC和AOP,换句话说,容器的两大特性就是IOC和AOP. ...