Monitor里边有一些static方法,可以用于在对象上获取同步锁,来进行一些进程同步控制操作
用法及注意点如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace myTest { class Program { // 一个比较容易犯的错误 // 使用 Monitor 锁定对象(即引用类型)而不是值类型。将值类型变量传递给 Enter 时, //它被装箱为对象。如果再次将相同的变量传递给 Enter,则它被装箱为一个单独对象,而且线程不会阻止。Monitor //本应保护的代码未受保护。此外,将变量传递给 Exit 时, //也创建了另一个单独对象。因为传递给 Exit 的对象和传递给 Enter 的对象不同,Monitor //将引发 SynchronizationLockException //这种情况最好用 Interlocked 来完成 private static int _num = 1; // 装箱一下就可以了 private static object num = _num; static void Main(string[] args) { Thread t1 = new Thread(new ThreadStart(addNum)); t1.Name = "线程1"; Thread t2 = new Thread(new ThreadStart(addNum)); t2.Name = "线程2"; t1.Start(); t2.Start(); Console.ReadLine(); } //|- 拥有锁的线程 lockObj->|- 就绪队列(ready queue) |- 等待队列(wait queue) // 就绪队列:尝试lock对象的线程 // 等待队列:在等待中, !!!不会主动!!! 去lock对象的线程 Monitor.wait 会使线程进入等待队列 // 如果只调用wait不调用pulse,可能使线程进入死锁 // 下面执行的时间线: t1获得锁——t1打印——2000ms——t1Pulse(此时无线程在等待队列,故无效)——2000ms——t1释放锁并进入等待队列—— // t2获得锁——t2打印——2000ms——t2Pulse(此时t1线程在等待队列,t1进入就绪队列)——2000ms——t2释放锁并进入等待队列—— // t1获得锁——t1Pulse(此时t2线程在等待队列,t2进入就绪队列)——t1打印——t1释放锁——t1退出—— // t2获得锁——t2Pulse(此时无线程在等待队列,故无效)——t2打印——t2释放锁——t2退出 private static void addNum() { Boolean gotLock = false; try { // Monitor.Enter(num); //获取排它锁 Monitor.Enter(num, ref gotLock); Console.WriteLine(Thread.CurrentThread.Name+ DateTime.Now.ToString() + "——————" + num); //释放锁并让线程进入等待队列,直到它重新获得锁 Thread.Sleep(2000); //通知等待的线程进入就绪队列,有锁了 Monitor.Pulse(num); Thread.Sleep(2000); Monitor.Wait(num); Monitor.Pulse(num); } finally { Console.WriteLine(Thread.CurrentThread.Name + DateTime.Now.ToString() + "——————" + num); if (gotLock) { Monitor.Exit(num); } } } } }
运行结果:
用Monitor类获得对象锁的try .. catch finally的过程还有一个语法糖,lock关键字