由于可能导致异常行为的产生,多线程技术显然对于开发人员来说提出了一系列新的挑战。本文,我们将就这些挑战之一:如何中断一个正在运行的线程展开讨论。
在Java中通过其内建的线程支持,编写多线程的程序还是相当简单的。然而,采用多线程技术将对程序开发人员提出了一些列的挑战,假如没有得到正确的处理,可能会导致异常行为的产生,以及难以发现的差错。本文,我们将就这些挑战之一:如何中断一个正在运行的线程展开讨论。
背景
中断一个线程意味着在完成其任务以前,停止线程正在进行的工作,即有效的中止当前操作。线程中断后是等待新的任务还是继续进行下一步操作将取决于应用程序。
尽管在最初看起来比较简单,你还是需要预先采取一些措施以求获得理想的结果。这里就你必须注重的问题提出了一些建议:
首先,不要使用Thread.stop方法。尽管它的确可以中止一个正在运行的线程,但这样的方法并不安全,并遭到了开发人员普遍的反对。这也可能意味着在未来的Java版本中它可能不会出现。
另一种并不建议的方法是Thread.interrupt。有人可能会将其与上文提到的方法相混淆。不论它的名字表示什么,这种方法事实上并没有立即中断一个正在运行的线程(后来也不会),如列表A所示。它创建了一个线程,并且尝试使用Thread.interrupt来停止此线程。对Thread.sleep()的调用提供了充裕的时间来进行线程的初始化和结束。线程本身并没有做任何有用的事情。
假如运行列表A中的代码,在控制台中你可以看到类似的如下内容:
Starting thread...
Thread is running...
Thread is running...
Thread is running...
Interrupting thread...
Thread is running...
Thread is running...
Thread is running...
Stopping application...
即使在调用Thread.interrupt()之后,线程还是运行了一段时间。
真正的中断一个线程
中断一个线程的最好的推荐方法是使用一个共享变量来指示线程必须中止目前所做的工作。线程必须周期性的对变量进行检查,尤其在处理较长的操作的时候,然后通过有序的方式中止线程任务。列表B的代码给出了此技术的具体实现:
运行列表B中的代码将会产生如下输出(注重线程在前一种方法中是如何退出的):
Starting thread...
Thread is running...
Thread is running...
Thread is running...
ASKINg thread to stop...
Thread exiting under request...
Stopping application...
尽管这样的方法需要编写一定量代码,但这并不会给执行这些线程以及根据需要对线程进行清除带来多大麻烦,而尤其是清除线程对于任何一个多线程的应用程序来说都是绝对必需的。只需要确保已经对共享变量声明为可变,或者将任何对其的访问封装在同步代码块或方法里面。
到现在为止,一切都很顺利。但假如线程被封锁以等待一些事件,那么将会发生些什么?当然,假如线程被封锁,它将不能对共享变量进行检查,从而无法停止。有很多时候会发生这样的情况,诸如对Object.wait()、ServerSocket.accept()以及DatagramSocket.receive()瞪函数进行调用的时候。
这些函数都能够将线程永远的封锁起来。即使采用了超时机制,或许也不会可行,或者让人无法忍受线程一直运行直到到达超时状态。所以必须采用某种机制以使线程提早的退出封锁状态。
不幸的是,这里还没有这样的机制能够适用于所有的情况。但可以根据具体的情况来使用一些特定的技巧。在如下的部分,我将给出对于绝大部分的常见情况所采取的解决方案。
通过Thread.interrupt()中断一个线程
如列表A所示,采用Thread.interrupt()方法并没有中断一个正在运行的线程。此方法事实上做的只是假如线程被封锁则抛出一个中断信号,由此线程退出了封锁状态。更为精确的讲,假如线程被封锁在方法Object.wait、Thread.join或是Thread.sleep,它将接收一个InterruptedException,从而提前终结封锁方法。
因此,假如一个线程被封锁在上述方法中的任意一个,停止它的正确方法是设置共享变量,并对其调用interrupt()方法(注重首先设置变量非常重要)。假如线程没有被锁定,调用interrupt()则无关紧要,否则,线程将会得到一个异常(线程本身必须预备好处理这种情况)然后退出锁定状态。在任何一种情况中,最终线程都将会检测共享变量并终止。列表C的简单范例程序表明了这一技术的运用。