内容参考自: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. 另辟蹊径 直取通州的“墨迹天气”APP应用的成功故事

    一个天气应用,曾被认为是要挑战国家气象局,网站也莫名其妙地被封,两个合伙人先后离开.创始人金犁是如何把这么一款工具类应用做到人所共知的? 采访 | 郑江波 翟文婷 文 | 翟文婷 出生时间:1982年 ...

  2. lnmp 一键安装

    系统需求: CentOS/RHEL/Fedora/Debian/Ubuntu/Raspbian/Deepin Server/Aliyun/Amazon/Mint Linux发行版 需要5GB以上硬盘剩 ...

  3. Ubuntu中开启和关闭防火墙-摘自网络

    1.关闭ubuntu的防火墙 ufw disable开启防火墙ufw enable 2.卸载了iptablesapt-get remove iptables3.关闭ubuntu中的防火墙的其余命令ip ...

  4. Android 控件: Webview 的一些知识点

    WebView 加载网页,当点击返回键的时,会显示上一个页面,并刷新. 同时可以对返回上一个页面进行干预,就是用到了缓存. webview加载网页的几个模式,即websetting中设置的加载模式.w ...

  5. Http Post 二进制通信

    客户端请求和接收(使用了httpclient4.3 和netty3.5) public static void httpPost11() { CloseableHttpClient httpClien ...

  6. Android xUtils3源代码解析之网络模块

    本文已授权微信公众号<非著名程序猿>原创首发,转载请务必注明出处. xUtils3源代码解析系列 一. Android xUtils3源代码解析之网络模块 二. Android xUtil ...

  7. [Windows Azure] Managing SQL Database using SQL Server Management Studio

    Managing Windows Azure SQL Database using SQL Server Management Studio You can use Windows Azure SQL ...

  8. (原创)C++11改进我们的程序之简化我们的程序(二)

    这次要讲的是:C++11如何通过组合函数来简化我们的程序.关于组合函数,大家可能对这个概念有点陌生.组合函数是将N个一元函数组成一种更复杂的函数,每个函数的返回值作为参数传给下一个函数,直到传到最后一 ...

  9. [MyBean说明书]-如何制作BPL插件

    DEMO位置:     samples\simpleConsole\Lib-bpl [步骤]: 1. 首先新建一个BPL工程. 2. 添加一个窗体,实现IPluginForm接口(simpleCons ...

  10. 【delphi】多线程同步之Semaphore

    另外两种多线程的同步方法 CriticalSection(临界区) 和 Mutex(互斥), 这两种同步方法差不多, 只是作用域不同; CriticalSection(临界区) 类似于只有一个蹲位的公 ...