线程基础四 使用Monitor类锁定资源
前面我们讲过了lock的用法以及竞争条件导致的错误,实际上lock关键字是Monitor类用例的一个语法糖。如果我们分解使用了lock关键字的代码,将会看到它如下面代码片段所示:
bool acquireLock = false; try { Monitor.Enter(lockObject, ref acquireLock); //Code that access resources that are protected by the lock. } finally { if (acquireLock) { Monitor.Exit(acquireLock); } }
在多线程编程中另一个常见的多线程错误被称为死锁(deadlock)。在以下例子中,将演示使用Monitor类来避免死锁,而之前描述过得lock用于创建死锁。
static void Main(string[] args) { object lock1 = new object(); object lock2 = new object(); new Thread(() => LockTooMuch(lock1, lock2)).Start(); lock (lock2) { Thread.Sleep(1000); Console.WriteLine(@"Monitor.TryEnter allows not to get stuck,returning false after a specified timeout is elapsed"); if (Monitor.TryEnter(lock1, TimeSpan.FromSeconds(5))) { Console.WriteLine("Acquired a protected resource successfully"); } else { Console.WriteLine("Timeout acquiring a resource!"); } } new Thread(() => LockTooMuch(lock1, lock2)).Start(); Console.WriteLine("-----------------------------"); lock (lock2) { Console.WriteLine("This will be a deadlock"); Thread.Sleep(1000); lock (lock1) { Console.WriteLine("Acquired a protected resouce successfully"); } } } static void LockTooMuch(object lock1, object lock2) { lock (lock1) { Thread.Sleep(1000); lock (lock2) ; } }
在这个例子中,先定义了一个LockTooMuch方法。在该方法中,我们先锁定了第一个对象,等待一秒后锁定了第二个对象。然后在另一个线程中启动该方法。最后尝试在主线程中先后锁定第二个和第一个对象。我们分为两部分,分别使用Montitor以及lock演示了如何避免死锁以及造成死锁。
首先先说说造成死锁的情况,在第二部分中,我们使用了lock关键字。通过new Thread新起的线程使得程序保持对lock1对象的锁定,等待直到lock2对象被释放。而此时主线程却又在保持对lock2对象的锁定并等待直到lock1对象被释放,但lock1却被新起的线程占用,等待lock2,因此lock1对象永远不会被释放,从而进入死锁状态。
因此,我们就可以通过第一部分的方式,采用Monitor类的TryEnter方法,该方法接受一个超时参数。如果我们不能在超市参数过期之前获取到被lock保护的资源,则该方法返回false。