25.5.4 死锁
上一节讲了线程同步技术,毋庸置疑,这是一个很好的机制,但使用不当也会带来困惑。死锁就是使用同步技术所带来的副作用之一。所谓死锁,就是两个线程互相等待对方释放锁定的对象。这种等待常常是“此恨连绵无绝期”,也就是说没有等到的那一天!还是让代码来说话吧,大家看代码清单25-15。
代码清单25-15 死锁示例代码
using System;
using System.Threading;
namespace ProgrammingCSharp4
{
class AsynchronousSample
{
private object_lock_1=new object();
private object_lock_2=new object();
public void Lock1()
{
Console.WriteLine(“线程{0}启动,执行Lock1()……”,
Thread.CurrentThread.ManagedThreadId);
lock(_lock_1)
{
System.Threading.Thread.Sleep(1000);
lock(_lock_2)
{
}
}
Console.WriteLine(“线程{0}从Lock1()退出”,
Thread.CurrentThread.ManagedThreadId);
}
public void Lock2()
{
Console.WriteLine(“线程{0}启动,执行Lock2()……”,
Thread.CurrentThread.ManagedThreadId);
lock(_lock_2)
{
System.Threading.Thread.Sleep(2000);
lock(_lock_1)
{
}
}
Console.WriteLine(“线程{0}从Lock2()退出”,
Thread.CurrentThread.ManagedThreadId);
}
static void Main(string[]args)
{
AsynchronousSample sample=new AsynchronousSample();
Thread[]threads=new Thread[2]{new Thread(sample.Lock1),new
Thread(sample.Lock2)};
threads.Start();
}
}
public static class ThreadUtil
{
public static void Start(this Array array)
{
foreach(object obj in array)
{
Thread thread=obj as Thread;
if(thread!=null)
{
thread.Start();
}
}
}
}
}
在上述代码中,有两个方法:Lock1()和Lock2(),分别锁定_lock_1和_lock_2。不同的是,在线程1调用的Lock1()方法中,先锁定_lock_1对象,在延迟1秒后再锁定_lock_2对象。与此同时,在线程2调用的Lock2()方法中,先锁定_lock_2对象,过2秒后再锁定_lock_1对象。那么下面会发生什么情况呢?在Lock1方法中1秒时间到开始申请锁定_lock_2对象时,发现对象已经被锁住,因此线程1进入等待状态;而当线程2中的Lock2方法2秒时间到,开始申请锁定_lock_1对象的时候,发现对象也被锁定,因此也进入等待状态。最终就导致,线程1等待线程2释放_lock_2对象,而线程2则等待线程1释放_lock_1对象。因此,上述代码输出如图25-8所示,调用线程对象的Abort方法将线程强制中断。
图 25-8 线程死锁
注意,图25-8中的线程4和线程3中的4和3均为线程编号,并非文中使用的1和2代号,这两者并无关联。
图25-9说明了这种死锁的情况。
图 25-9 死锁示意图
知道了症结所在,就可以对症下药了。例如可以改变锁定的顺序,在Lock2方法中由原来的先锁定_lock_1再锁定_lock_2,调换这个顺序,如代码清单25-16所示。
代码清单25-16 改变Lock2方法中的锁定顺序
public void Lock2()
{
Console.WriteLine(“线程{0}启动,执行Lock2()……”,
Thread.CurrentThread.ManagedThreadId);
lock(_lock_1)
{
System.Threading.Thread.Sleep(2000);
lock(_lock_2)
{
}
}
Console.WriteLine(“线程{0}从Lock2()退出”,
Thread.CurrentThread.ManagedThreadId);
}
修改后,再运行,则死锁不再发送,运行结果如图25-10所示。
图 25-10 修改后再次的运行结果