C#中, 通过System.Threading.Monitor类可以实现多线程中对某些代码块的同步访问,以确保数据的安全性。
object obj=new object();
Monitor在锁对象obj上会维持两个线程队列R和W以及一个引用T :
(1) T是对当前获得了obj锁的线程的引用(设此线程为CurrThread);
(2) R为就绪队列, 其上的线程已经准备好获取obj锁。当obj锁被CurrThread释放后(CurrThread可通过Monitor.Exit(obj)或 Monitor.Wait(obj)来释放其所获的obj锁)这些线程就会去竞争obj锁,获得obj锁的线程将被T引用; 线程调用Monitor.Enter(obj)或Monitor.TryEnter(obj)将会使该线程直接进入R队列。
(3) W为等待队列,其上的线程是因为调用了Monitor.Wait(obj)而进入W队列的;W上的线程不会被OS直接调度执行,也就是说它们没有准备好获取obj锁,就是说在等待队列上的线程不能去获得obj锁。当前获得obj锁的线程CurrThread调用Monitor.Pulse(obj)或Monitor.PulseAll(obj)后会使W队列中的第一个等待线程或所有等待线程被移至R队列,这时被移至R队列的这些线程就有机会被OS直接调度执行,也就是有可以去竞争obj锁。
lock 关键字可以作为Monitor类的一个替代。下面两个代码块是等效的:
Monitor.Enter(this);
//...
Monitor.Exit(this);
lock (this)
{
//...
}
在这里,object 值与 lock 中的 object 值是一样的。 简而言之,lock 的写法是 Monitor 类的一种简写。 【实例】将上一节《C# lock》实例中的 lock 关键字替换成 Monitor 类。 根据题目要求,代码如下。
class Program
{
public void PrintEven()
{
Monitor.Enter(this);
try
{
for(int i = 0; i <= 10; i = i + 2)
{
Console.WriteLine(Thread.CurrentThread.Name + "--" + i);
}
}
finally
{
Monitor.Exit(this);
}
}
public void PrintOdd()
{
Monitor.Enter(this);
try
{
for(int i = 1; i <= 10; i = i + 2)
{
Console.WriteLine(Thread.CurrentThread.Name + "--" + i);
}
}
finally
{
Monitor.Exit(this);
}
}
static void Main(string[] args)
{
Program program = new Program();
ThreadStart ts1 = new ThreadStart(program.PrintOdd);
Thread t1 = new Thread(ts1);
t1.Name = "打印奇数的线程";
t1.Start();
ThreadStart ts2 = new ThreadStart(program.PrintEven);
Thread t2 = new Thread(ts2);
t2.Name = "打印偶数的线程";
t2.Start();
}
}
运行该程序,效果如下图所示。
Monitor 类的TryEnter() 方法在尝试获取一个对象上的显式锁方面和 Enter() 方法类似。然而,它不像Enter()方法那样会阻塞执行。如果线程成功进入关键区域那么TryEnter()方法会返回true.
Monitor 类的用法虽然比 lock 关键字复杂,但其能添加等待获得锁定的超时值,这样就不会无限期等待获得对象锁。 使用 TryEnter() 方法可以给它传送一个超时值,决定等待获得对象锁的最长时间。 使用 TryEnter() 方法设置获得对象锁的时间的代码如下。
Monitor.TryEnter(object, 毫秒数 );
该方法能在指定的毫秒数内结束线程,这样能避免线程之间的死锁现象。 此外,还能使用 Monitor 类中的 Wait() 方法让线程等待一定的时间,使用 Pulse() 方法通知处于等待状态的线程。
1.Monitor.Enter(object)方法是获取锁,Monitor.Exit(object)方法是释放锁,这就是Monitor最常用的两个方法,当然在使用过程中为了避免获取锁之后因为异常,致锁无法释放,所以需要在try{} catch(){}之后的finally{}结构体中释放锁(Monitor.Exit())。
2.Monitor的常用属性和方法:
Enter(Object) 在指定对象上获取排他锁。
Exit(Object) 释放指定对象上的排他锁。
IsEntered 确定当前线程是否保留指定对象锁。
Pulse 通知等待队列中的线程锁定对象状态的更改。
PulseAll 通知所有的等待线程对象状态的更改。
TryEnter(Object) 试图获取指定对象的排他锁。
TryEnter(Object, Boolean) 尝试获取指定对象上的排他锁,并自动设置一个值,指示是否得到了该锁。
Wait(Object) 释放对象上的锁并阻止当前线程,直到它重新获取该锁。
Lock关键字
1.Lock关键字实际上是一个语法糖,它将Monitor对象进行封装,给object加上一个互斥锁,A进程进入此代码段时,会给object对象加上互斥锁,此时其他B进程进入此代码段时检查object对象是否有锁?如果有锁则继续等待A进程运行完该代码段并且解锁object对象之后,B进程才能够获取object对象为其加上锁,访问代码段。
2.Lock关键字封装的Monitor对象结构如下:
try
{
Monitor.Enter(obj);
dosomething();
}
catch(Exception ex)
{
}
finally
{
Monitor.Exit(obj);
}
3.锁定的对象应该声明为private static object obj = new object();尽量别用公共变量和字符串、this、值类型。
Monitor和Lock的区别
1.Lock是Monitor的语法糖。
2.Lock只能针对引用类型加锁。
3.Monitor能够对值类型进行加锁,实质上是Monitor.Enter(object)时对值类型装箱。
4.Monitor还有其他的一些功能。
本文代码示例:
class Program
{
private static object obj = new object();
public void LockSomething()
{
lock (obj)
{
dosomething();
}
}
public void MonitorSomeThing()
{
try
{
Monitor.Enter(obj);
dosomething();
}
catch(Exception ex)
{
}
finally
{
Monitor.Exit(obj);
}
}
public void dosomething()
{
//做具体的事情
}
}