C# Monitor:锁定资源

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 关键字

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(); }}

运行该程序,效果如下图所示。

C# Monitor:锁定资源

Monitor 类的TryEnter() 方法在尝试获取一个对象上的显式锁方面和 Enter() 方法类似。然而,它不像Enter()方法那样会阻塞执行。如果线程成功进入关键区域那么TryEnter()方法会返回true.

Monitor 类的用法虽然比 lock 关键字复杂,但其能添加等待获得锁定的超时值,这样就不会无限期等待获得对象锁。

使用 TryEnter() 方法可以给它传送一个超时值,决定等待获得对象锁的最长时间。

使用 TryEnter() 方法设置获得对象锁的时间的代码如下。

Monitor.TryEnter(object, 毫秒数 );

该方法能在指定的毫秒数内结束线程,这样能避免线程之间的死锁现象。

此外,还能使用 Monitor 类中的 Wait() 方法让线程等待一定的时间,使用 Pulse() 方法通知处于等待状态的线程。

 C#中Monitor和Lock简介及区别

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()        {             //做具体的事情        }    }