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.5.4 死锁 - 图1

图 25-8 线程死锁

注意,图25-8中的线程4和线程3中的4和3均为线程编号,并非文中使用的1和2代号,这两者并无关联。

图25-9说明了这种死锁的情况。

25.5.4 死锁 - 图2

图 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.5.4 死锁 - 图3

图 25-10 修改后再次的运行结果