当主线程瓦解而其它线程继承运行时产生什么
副标题#e#
孤线程模式
症状
治疗和防范法子
总结
参考资料
关于作者
对本文的评价
当主线程瓦解而其它线程继承运行时产生什么?
Eric E. Allen([email protected])
博士研究生候选人,Rice 大学
2001 年 8 月
在多线程代码中,利用驱动其它线程所认真的行动的单个主线程是常见的。这个主线程发送动静,凡是是通过把它们放到一个行列中,然后其它线程处理惩罚这些动静。可是假如主线程抛出一个异常,那么剩余的线程会继承运行,期待更多输入到该行列,导致措施冻结。在诊断 Java 代码的这一部门中,专职 Java 开拓者兼兼职捉虫者 Eric Allen 接头检测、修复和制止这一错误模式。请在接头论坛与作者和其他读者共享您关于本文的心得。
用多线程编写代码对措施员大有长处。多线程能使编程(和措施)举办得快得多,并且代码能有效得多地利用资源。然而,跟糊口中的许多工作一样,多线程也存在缺点。因为多线程代码天发展短确定性的,呈现错误的大概性大得多。并且,确实产生的的错误很难重现,因此也更难办理。
孤线程模式
Java 编程语言为多线程代码提供了富厚的支持,包罗一项出格有用的成果:可以或许在一个线程中抛出一个异常而不影响其它线程。但这项成果会导致许多灾以跟踪的错误。
快速跟踪代码
清单 1.
一个线程之间频繁通信的示例措施
清单 2.
演示如何捕获异常并在退出之前通知问题的依赖线程。
从某个线程的瓦解中规复过来是有意义,在此种环境下,这种本领能增加措施的结实性级别。然而,它也使我们难以判定这些线程之一在什么时候抛出了一个异常。因为剩余的线程将继承运行,所以措施会表示出无响应或冻结措施的征兆。对线程之间频繁通信的措施而言尤其如此。
思量清单 1 所示的示例,个中的一对线程通过出产者-消费者模子举办通信。
清单 1. 一个简朴的、多线程的消费者-出产者措施
public class Server extends Thread {
Client client;
int counter;
public Server(Client _client) {
this.client = _client;
this.counter = 0;
}
public void run() {
while (counter < 10) {
this.client.queue.addElement(new Integer(counter));
counter++;
}
throw new RuntimeException("counter >= 10");
}
public static void main(String[] args) {
Client c = new Client();
Server s = new Server(c);
c.start();
s.start();
}
}
class Client extends Thread {
Vector queue;
public Client() {
this.queue = new Vector();
}
public void run() {
while (true) {
if (! (queue.size() == 0)) {
processNextElement();
}
}
}
private void processNextElement() {
Object next = queue.elementAt(0);
queue.removeElementAt(0);
System.out.println(next);
}
}
在诸如这样的案例中,第二个线程吸收用于计较的任何数据完全依赖于第一个线程。因此,不行制止地,假如第一个线程瓦解(而在这个样本中,必定是这样的),那么第二个线程将期待永远不会到来的更多输入。此刻您知道我为什么把这种错误叫做孤线程模式。
症状
这种错误模式最常见的症状是我在前面提到的 ? 即,措施好象冻结了。
其它症状大概包罗打印到措施尺度错误和尺度输出的仓库跟踪实际遏制了。
#p#副标题#e#
治疗和防范法子
一旦诊断出这种错误模式,查找并修复在瓦解线程中的潜在的错误是显然的治疗之道。可是防范却坚苦得多。
不消说,假如您利用单线程设计就能荣幸乐成,那么将可以免去许多头痛的工作。然而,当措施的机能要求是必需思量的问题时,就要首先思量利用多线程设计。
诊断这种瓦解的一个帮助手段是捕获由各类线程抛出的异常并在退出之前通知该问题的依赖线程。这正是我在清单 2 中所做的。
清单 2. 把错误通知给客户机线程的示例
import java.util.Vector;
public class Server2 extends Thread {
Client2 client;
int counter;
public Server2(Client2 _client) {
this.client = _client;
this.counter = 0;
}
public void run() {
try {
while (counter < 10) {
this.client.queue.addElement(new Integer(counter));
counter++;
}
throw new RuntimeException("counter >= 10");
}
catch (Exception e) {
this.client.interruptFlag = true;
throw new RuntimeException(e.toString());
}
}
public static void main(String[] args) {
Client2 c = new Client2();
Server2 s = new Server2(c);
c.start();
s.start();
}
}
class Client2 extends Thread {
Vector queue;
boolean interruptFlag;
public Client2() {
this.queue = new Vector();
this.interruptFlag = false;
}
public void run() {
while (! interruptFlag) {
if (! (queue.size() == 0)) {
processNextElement();
}
}
// Processes whatever elements remain on the queue before exiting.
while (! (queue.size() == 0)) {
processNextElement();
}
System.out.flush();
}
private void processNextElement() {
Object next = queue.elementAt(0);
queue.removeElementAt(0);
System.out.println(next);
}
}
#p#分页标题#e#
处理惩罚被抛出的异常的其它选项大概是挪用 System.exit。这个选项在措施的主线程产生瓦解而其它线程不打点任何临界资源的时候是有意义的。然而在其它环境下,这大概是危险的。譬喻,思量这样一个示例,其它线程中的一个正在打点一个打开的文件。假如这是实际的环境,那么只是退出措施会导致资源泄漏。
纵然在上面的简朴示例中,在 server 线程中挪用 System.exit 也会导致 client 未处理惩罚其行列上的任何剩余元素就退出。
事实上,就是这样的问题促使 Sun 不发起线程的 stop 要领。由于强行遏制一个线程会使资源陷入非一致状态,所以 stop 要领粉碎了语言的安详性模子。
想相识 Sun 的更多不发起来由,请参阅参考资料。
总结
这里是本周错误模式的总结:
模式:孤线程
症状:一个锁定多线程措施,它具有或不具有将仓库跟踪打印到尺度错误。
致因:多个措施线程一直期待来自某个线程的输入,而该线程在抛出一个未被捕获的异常后就退出了。
治疗和防范法子:把异常处理惩罚代码放到主线程中以在瓦解光降之际通知依赖线程。
参考资料
介入本文的接头论坛。
阅读关于为什么不发起 Thread.stop 的 Sun 的表明。
Neel V. Kumar 在他的文章“Java 措施中的多线程”(developerWorks,2000 年 3 月)中提供调试多线程 Java 的途径。
Peter Haggar 的“优化 Java 编程中的并发”(IBM PartnerWorld for Developers)是一份优秀的白皮书,它接头通过执行多线程来并发存取数据会导致的常见问题。
想得到编写多线程 Java 措施的先容,请参阅 Alex Roetter 的文章“编写多线程 Java 应用措施”(developerWorks,2001 年 2 月)。
想为您的 Java 应用措施中的多线程问题得到技能辅佐,请会见多线程 Java 编程接头论坛。
Brian Goetz 在他共三部门的系列轻松利用线程中处理惩罚坚苦的线程问题。
JUnit 主页提供接头措施测试要领的许多有趣文章的链接,并提供 JUnit 的最新版本。
操作 Java 调试教程(developerWorks,2001 年 2 月),获取通用调试技能的辅佐。
阅读 Eric 所有诊断 Java 代码的文章,个中大都着重接头错误模式。
请在 developerWorks Java 技能专区查找更多的 Java 参考资料。
关于作者
Eric Allen 从 Cornell 大学得到计较机科学及数学的学士学位,而且是 Rice 大学 Java 编程语言小组的博士候选人。在回 Rice 专心攻读学位前,Eric 是 Cycorp,Inc. 的 Java 开拓者带头人。他还在 JavaWorld 上主持 Java 初学者接头论坛。他的研究包罗在源措施和字节码级别上 Java 语言的语义模子和静态阐明东西开拓。Eric 还辅佐开拓 Rice 的 NextGen 编程语言编译器,NextGen 是一个支持泛运行时范例的 Java 扩展。可通过 [email protected] 与 Eric 接洽。