练习 1.4:解决线程瓶颈
在开始之前,必须完成练习 1.3:识别线程瓶颈。
要找到代码中的死锁,除了使用“线程视图”之外,还可以使用“UML2 时序图”(“对象交互与线程交互”视图)和“调用堆栈视图”。
要解决此死锁,请首先找出问题涉及的方法调用和对象:
- 在“线程视图”中,查找进入“死锁”状态的第一个 philo* 线程。将光标停留在“死锁”段上。工具提示指出锁定(Fork.<id number>)和锁定线程(Locking Thread.<name>),例如:
Lock: Fork 10038
Locking Thread: philo#1
- 右键单击要运行的“概要分析”资源并选择打开方式 > UML2 对象交互。
打开“UML2 时序图”视图,显示“对象交互”。
- 在时序图中,水平滚动视图以查找 Fork.<id number>,并单击以选择它。

- 向下滚动查找从一个 philo* 线程指向 Fork.<id number> 的水平箭头。
此箭头显示对象间的交互,并且这两个对象之间的第一个交互将指出 philo* 线程已获取 Fork.<id number>。
您将发现带有 getName 标签的箭头。

- 单击 getName。在“线程视图”中,垂直的“当前时间”指示符移至显示,以查看调用 getName 时在整个程序中发生的事情。您将看到请求成功,因为发出请求的 philo* 线程没有进入“等待锁定”或“死锁”状态。

- 在时序图中,再次单击 Fork.<id number>,并向下滚动以查找在不同的 philo* 线程中发出的 getName 请求。
- 单击 getName 的此实例。“当前时间”指示符将显示发出请求的 philo* 线程没有获取 Fork.<id number>,并且进入“等待锁定”状态,然后进入“死锁”状态。
在此实例中,问题在于另一线程占用了 Fork 的请求。检查其它已进入死锁的线程,以验证在其它实例中也是这样。
现在我们来尝试查找导致此问题的方法:
- 右键单击要运行的“概要分析”资源并选择打开方式 > UML2 线程交互。
打开“UML2 时序图”视图,显示“线程交互”。
- 在“线程视图”中,单击菜单下拉按钮并单击打开调用堆栈视图。
- 在显示线程名称的“线程视图”中,双击第一个进入死锁的 philo* 线程。注意“线程交互”视图将更改为只显示此线程的信息。
- 向下滚动到线程信息的最后,并双击此线程运行的最后一个方法:run 方法。“调用堆栈视图”此时将显示堆栈中的所有调用。
- 在“调用堆栈”中,注意到占有锁的线程已调用了 Philosopher.java 中的 Sleep 方法;或者也已经进入死锁并因此没有执行任何操作。
- 检查以死锁状态结束运行的其它线程;Philosopher.java 中的 Sleep 方法经常在“调用堆栈”中,可能是问题所在。
我们现在怀疑 Sleep 方法。我们来查看一下代码:
- 在“调用堆栈”中,右键单击 Sleep(int) void [Philosopher.java] 的实例并选择打开源代码。将在编辑器的 Sleep 类位置处打开源代码。
- 检验代码。请注意,Sleep 方法是由 run 方法调用的。
首先调用的是 trace,它打印出消息“got left...”,然后调用 Sleep。
通过注释掉 Sleep 的调用,我们可能能够阻止死锁。
- 注释掉 Sleep。
- 选择文件 > 保存。
现在,请对您的程序再次执行概要分析。
这次运行没有出现死锁,并且将以下内容写入控制台:
HeadWaiter 报告所有 philosopher 已正常完成
正如您所见,“线程视图”和其它视图向您显示您的程序在运行时线程发生的问题。
基于您对程序的认识,是否执行分析和解决死锁完全由您来决定。
通过查看总结中的内容来完成教程。