.NET中lock的使用方法及注意事项

lock就是把一段代码定义为临界区,所谓临界区就是同一时刻只能有一个线程来操作临界区的代码,当一个线程位于代码的临界区时,另一个线程不能进入临界区,如果试图进入临界区,则只能一直等待(即被阻止),直到已经进入临界区的线程访问完毕,并释放锁旗标。

其基本使用方式如下:

C-sharp代码

  1. class Test
  2. {
  3. //定义一个私有成员变量,用于Lock
  4. private static object lockobj = new object();
  5. void DoSomething()
  6. {
  7. lock (lockobj)
  8. {
  9. //需要锁定的代码块
  10. }
  11. }
  12. }

class Test
{
//定义一个私有成员变量,用于Lock
private static object lockobj = new object();
void DoSomething()
{
lock (lockobj)
{
//需要锁定的代码块
}
}
}

最经典的例子,莫过于模拟银行5个窗口取钱操作的例子了,5个窗口是5个线程,都要取钱,但是同一刻只能有一个窗口可以进行真正的取钱操作(钱数的数值计算,剩余多少等这些代码必须定义为临界区),其他只有等待,其代码如下:

C-sharp代码

  1. using System;
  2. using System.Threading;
  3. class Account
  4. {
  5. int balance;
  6. Random r = new Random();
  7. public Account(int initial)
  8. {
  9. balance = initial;
  10. }
  11. int Withdraw(int amount)
  12. {
  13. // This condition will never be true unless the lock statement
  14. // is commented out:
  15. if (balance < 0)
  16. {
  17. throw new Exception("Negative Balance");
  18. }
  19. // Comment out the next line to see the effect of leaving out
  20. // the lock keyword:
  21. lock (this)
  22. {
  23. if (balance >= amount)
  24. {
  25. Console.WriteLine("提款之前余额(Balance before Withdrawal):  " + balance);
  26. Console.WriteLine("提款数量(Amount to Withdraw)           : -" + amount);
  27. balance = balance - amount;
  28. Console.WriteLine("提款之后余额(Balance after Withdrawal) :  " + balance);
  29. Console.WriteLine();
  30. return amount;
  31. }
  32. else
  33. {
  34. return 0; // transaction rejected
  35. }
  36. }
  37. }
  38. public void DoTransactions()
  39. {
  40. //模拟100个人来提款,每次提[1-10)元
  41. for (int i = 0; i < 100; i++)
  42. {
  43. Withdraw(r.Next(1, 10));
  44. }
  45. }
  46. }
  47. class Test
  48. {
  49. public static void Main()
  50. {
  51. Thread[] threads = new Thread[5];
  52. //总额为100元
  53. Account acc = new Account (100);
  54. //定义并初始化5个线程,模拟银行的5个窗口
  55. for (int i = 0; i < 5; i++)
  56. {
  57. Thread t = new Thread(new ThreadStart(acc.DoTransactions));
  58. threads[i] = t;
  59. }
  60. //启动5个线程,模拟银行的5个窗口开始工作
  61. for (int i = 0; i < 5; i++)
  62. {
  63. Console.WriteLine("threads[{0}].Start()", i);
  64. threads[i].Start();
  65. }
  66. }
  67. }

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)的时候,类的成员变量的值可能会被不在临界区的方法改值了

如下面的测试:

C-sharp代码

  1. using System.Threading;
  2. using System;
  3. public class ThreadTest
  4. {
  5. private int i = 0;
  6. public void Test()
  7. {
  8. Thread t1 = new Thread(Thread1);
  9. Thread t2 = new Thread(Thread2);
  10. t1.Start();
  11. t2.Start();
  12. }
  13. public void Thread1()
  14. {
  15. lock (this)
  16. {
  17. Console.WriteLine(this.i);
  18. Thread.Sleep(1000);
  19. Console.WriteLine(this.i);
  20. }
  21. }
  22. public void Thread2()
  23. {
  24. Thread.Sleep(500);
  25. this.i = 1;
  26. Console.WriteLine("Change the value in locking");
  27. }
  28. }
  29. public class ThreadTest2
  30. {
  31. private int i = 0;
  32. public void Test()
  33. {
  34. Thread t1 = new Thread(Thread1);
  35. Thread t2 = new Thread(Thread2);
  36. t1.Start();
  37. t2.Start();
  38. }
  39. public void Thread1()
  40. {
  41. lock (this)
  42. {
  43. Console.WriteLine(this.i);
  44. Thread.Sleep(1000);
  45. Console.WriteLine(this.i);
  46. }
  47. }
  48. public void Thread2()
  49. {
  50. lock (this)
  51. {
  52. Thread.Sleep(500);
  53. this.i = 1;
  54. Console.WriteLine("Can‘t change the value in locking");
  55. }
  56. }
  57. }
  58. public class ThreadMain
  59. {
  60. public static void Main()
  61. {
  62. //ThreadTest b = new ThreadTest();
  63. //Thread t = new Thread(new ThreadStart(b.Test));
  64. //t.Start();
  65. ThreadTest2 b2 = new ThreadTest2();
  66. Thread t2 = new Thread(new ThreadStart(b2.Test));
  67. t2.Start();
  68. }
  69. }

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的使用方法及注意事项

时间: 2024-10-09 16:55:11

.NET中lock的使用方法及注意事项的相关文章

jQuery 中get 和post 方法传值注意事项

用 jQuery 的都知道,jQuery 的 get 和 post 方法有三个参数:地址,数据 和回调函数,但我们知道地址也可以跟随数据的(形如:get_data.php?v1=1&v2=2),而且第二个参数可以省略,即第二个参数可 以直接写回调函数,那么数据写在地址后面和写在 data 参数里有什么区别呢? 刚刚做了几个实验,看看下面的代码就清楚了:以下内容需要回复才能看到 jquery_data.php echo "post: ";print_r($_POST);echo

linux中安装samba的方法以及注意事项

linux中安装软件的方法有很多种:1. 用rpm包直接进行安装(rpm -ivh package):2.yum安装:3.apt-get等. 如果能够联网,用apt-get是最简单的方法.但如果无法联网怎么办呢? 如果采用方法1进行安装,需要下载安装所需包及其依赖包:如果版本不符,还需要不断验证对应可行的版本,较为繁琐复杂,因此不太建议采用该方案. 因此,这里采用了yum进行安装. 使用yum安装,也需要有一个可靠的源,可在/etc/yum.repos.d/中新建myyum.repo文件,并写入

PHP中$_FILES的使用方法及注意事项说明

$_FILES:经由 HTTP POST 文件上传而提交至脚本的变量,类似于旧数组$HTTP_POST_FILES 数组(依然有效,但反对使用)详细信息可参阅 POST方法上传 $_FILES数组内容如下: $_FILES['myFile']['name']           客户端文件的原名称 $_FILES['myFile']['type']             文件的 MIME类型,需要浏览器提供该信息的支持,例如"image/gif" $_FILES['myFile'][

VC++对话框中添加状态栏的方法

方法一:1.添加成员变量CStatusBarCtrl m_StatusBar;2.在OnInitDialog()中加入:                  m_StatusBar.Create(WS_CHILD|WS_VISIBLE|SBT_OWNERDRAW, CRect(0,0,0,0), this, 0); int strPartDim[3]= {100, 200, -1}; //分割数量                  m_StatusBar.SetParts(3, strPartDi

ASP.net中网站访问量统计方法代码(在线人数,本月访问,本日访问,访问流量,累计访问)

一.建立一个数据表IPStat用于存放用户信息 我在IPStat表中存放的用户信息只包括登录用户的IP(IP_Address),IP来源(IP_Src)和登录时间 (IP_DateTime),些表的信息本人只保存一天的信息,如果要统计每个月的信息则要保存一个月.因为我不太懂对数据日志的操作,所以创建此表,所 以说我笨吧,哈哈. 二.在Global.asax中获取用户信息 在Global.asax的Session_Start即新会话启用时获取有关的信息,同时在这里实现在线人数.访问总人数的增量统计

Java并发包中Lock的实现原理

Lock 的简介及使用 Lock是java 1.5中引入的线程同步工具,它主要用于多线程下共享资源的控制.本质上Lock仅仅是一个接口(位于源码包中的java\util\concurrent\locks中),它包含以下方法 //尝试获取锁,获取成功则返回,否则阻塞当前线程 void lock(); //尝试获取锁,线程在成功获取锁之前被中断,则放弃获取锁,抛出异常 void lockInterruptibly() throws InterruptedException; //尝试获取锁,获取锁成

c#初学-多线程中lock用法的经典实例

本文转载自:http://www.cnblogs.com/promise-7/articles/2354077.html 一.Lock定义     lock 关键字可以用来确保代码块完成运行,而不会被其他线程中断.它可以把一段代码定义为互斥段(critical section),互斥段在一个时刻内只允许一个线程进入执行,而其他线程必须等待.这是通过在代码块运行期间为给定对象获取互斥锁来实现的. 在多线程中,每个线程都有自己的资源,但是代码区是共享的,即每个线程都可以执行相同的函数.这可能带来的问

java.util.concurrent.locks.Lock类的lock和lockInterruptibly方法的区别

什么是可中断的锁获取呢?就是:线程在等待获取锁的过程中,是否能够响应中断,以便在被中断的时候能够解除阻 塞状态,而不是傻傻地一直在等待.java对象的内置锁(synchronized)就是一种不可中断的锁,也就是说如果一个线 程在等待获取某个对象的内置锁,就算是该线程被其他线程中断,该线程仍然继续等待内置锁,而不是解除阻塞状 态,也不会抛出InterruptedException.Lock类的lock()类似synchronized,是不可中断的,在等待获取锁的过程中, 不响应中断请求:lock

AntiXSS v4.0中Sanitizer.GetSafeHtmlFragment等方法将部分汉字编码为乱码的解决方案

AntiXSS v4.0中Sanitizer.GetSafeHtmlFragment等方法将部分汉字编码为乱码的解决方案 以下代码为asp.net环境下,c#语言编写的解决方案.数据用Dictionary,而不是用其他的例如ArrayList存放,是为了速度.将Dictionary替换为HashTable也是一个不错的解决方案调用举例 String abc = //....你的某些赋值行为,这里的字符串是要处理的内容.............abc=Sanitizer.GetSafeHtmlFr