由于可能导致异常行为的产生,多线程技术显然对于开发人员来说提出了一系列新的挑战。本文,我们将就这些挑战之一:如何中断一个正在运行的线程展开讨论。
在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的简单范例程序表明了这一技术的运用。
一旦Thread.interrupt()在列表C代码中得到调用,线程将获得一个异常,于是它退出了封锁状态并决定它应当停止。运行这些代码将输出一下结果:
Starting thread...
Thread running...
Thread running...
Thread running...
Asking thread to stop...
Thread interrupted...
Thread exiting under request...
Stopping application...
对I/O操作进行中断
但如果线程是在执行I/O操作时被封锁将如何解决?I/O可以在一段可观的时间里保持对一个线程的封锁,尤其涉及网络通信的时候。比如,一个服务器可能会等待用户请求,或者一个网络应用程序会等待远程主机的响应。
如果你正在使用通道——在Java 1.4中可以获得的新的I/O API——封锁的线程将会得到一个ClosedByInterruptException异常。如果是这种情况,处理的逻辑方法与在第三个例子中所使用的相同——不同的仅仅是产生的异常有所区别。
但是,由于新的I/O最近才发布并且还有待进一步研究,你也可能还在使用Java 1.0以来一直提供的传统的I/O。这种情况下,使用Thread.interrupt()不会产生任何帮助,原因是线程将不会退出封锁状态。列表D中的代码显示了这一过程。尽管调用了interrupt()方法,线程还是没有退出封锁状态。
值得庆幸的是,Java Platform提供了在这种情况下的解决方案:通过调用锁定线程的socket的close()方法。这样的情况下,如果线程是在进行I/O操作时被锁定,线程将得到一个SocketException异常,这非常类似于interrupt()方法引发了InterruptedException异常的情况。
唯一需要提醒的地方是对socket的引用必须可用,这样的话close()方法才能被调用。这也意味着socket对象也必须被共享。列表E显示了这种情况。处理的逻辑过程与以前给出的范例相同。
运行列表E中代码将会得到如下预期结果:
Starting thread...
Waiting for connection...
Asking thread to stop...
accept() failed or interrupted...
Thread exiting under request...
Stopping application...
多线程是功能非常强大的工具,但也给自身的处理带来了一些列挑战。其中之一便是如何中断一个正在运行的线程。如果处理得当,使用这些技术中断线程的难度将不会大于使用Java Platform提供的内建操作完成同样的任务。
列表A:通过Thread.interrupt()中断线程
class Example1 extends Thread {
public static void main( String args[] ) throws Exception {
Example1 thread = new Example1();
System.out.println( "Starting thread..." );
thread.start();
Thread.sleep( 3000 );
System.out.println( "Interrupting thread..." );
thread.interrupt();
Thread.sleep( 3000 );
System.out.println( "Stopping application..." );
System.exit( 0 );
}
public void run() {
while ( true ) {
System.out.println( "Thread is running..." );
long time = System.currentTimeMillis();
while ( System.currentTimeMillis()-time < 1000 ) {
}
}
}
}
列表B:通过传递信号中断线程
class Example2 extends Thread {
volatile boolean stop = false;
public static void main( String args[] ) throws Exception {
Example2 thread = new Example2();
System.out.println( "Starting thread..." );
thread.start();
Thread.sleep( 3000 );
System.out.println( "Asking thread to stop..." );
thread.stop = true;
Thread.sleep( 3000 );
System.out.println( "Stopping application..." );
System.exit( 0 );
}
public void run() {
while ( !stop ) {
System.out.println( "Thread is running..." );
long time = System.currentTimeMillis();
while ( (System.currentTimeMillis()-time < 1000) && (!stop) ) {
}
}
System.out.println( "Thread exiting under request..." );
}
}
列表C:通过Thread.interrupt()退出封锁状态
class Example3 extends Thread {
volatile boolean stop = false;
public static void main( String args[] ) throws Exception {
Example3 thread = new Example3();
System.out.println( "Starting thread..." );
thread.start();
Thread.sleep( 3000 );
System.out.println( "Asking thread to stop..." );
thread.stop = true;
thread.interrupt();
Thread.sleep( 3000 );
System.out.println( "Stopping application..." );
System.exit( 0 );
}
public void run() {
while ( !stop ) {
System.out.println( "Thread running..." );
try {
Thread.sleep( 1000 );
} catch ( InterruptedException e ) {
System.out.println( "Thread interrupted..." );
}
}
System.out.println( "Thread exiting under request..." );
}
}
列表D:通过Thread.interrupt()中断I/O操作
import java.io.*;
class Example4 extends Thread {
public static void main( String args[] ) throws Exception {
Example4 thread = new Example4();
System.out.println( "Starting thread..." );
thread.start();
Thread.sleep( 3000 );
System.out.println( "Interrupting thread..." );
thread.interrupt();
Thread.sleep( 3000 );
System.out.println( "Stopping application..." );
System.exit( 0 );
}
public void run() {
ServerSocket socket;
try {
socket = new ServerSocket(7856);
} catch ( IOException e ) {
System.out.println( "Could not create the socket..." );
return;
}
while ( true ) {
System.out.println( "Waiting for connection..." );
try {
Socket sock = socket.accept();
} catch ( IOException e ) {
System.out.println( "accept() failed or interrupted..." );
}
}
}
}
列表E:I/O操作失败
import java.net.*;
import java.io.*;
class Example5 extends Thread {
volatile boolean stop = false;
volatile ServerSocket socket;
public static void main( String args[] ) throws Exception {
Example5 thread = new Example5();
System.out.println( "Starting thread..." );
thread.start();
Thread.sleep( 3000 );
System.out.println( "Asking thread to stop..." );
thread.stop = true;
thread.socket.close();
Thread.sleep( 3000 );
System.out.println( "Stopping application..." );
System.exit( 0 );
}
public void run() {
try {
socket = new ServerSocket(7856);
} catch ( IOException e ) {
System.out.println( "Could not create the socket..." );
return;
}
while ( !stop ) {
System.out.println( "Waiting for connection..." );
try {
Socket sock = socket.accept();
} catch ( IOException e ) {
System.out.println( "accept() failed or interrupted..." );
}
}
System.out.println( "Thread exiting under request..." );
}
}