使用Java内置支持的线程写多线程程序是很常见的事情。然而,多线程给开发人员带来了一些新的挑战。如果处理不好就会导致超出预期的行为以及难于定位的错误。这篇文章解读了其中一个挑战:如何中止一个正在运行的线程。
背景
中止一个线程意味着在线程处理完任务之前停掉正在做的操作,特别是放弃当前的操作。之后无论线程死掉,等待新的任务,或者执行下一步取决于应用程序的逻辑。
虽然这看起来非常简单,但是你必须做好防范措施,以便达到预期的效果。而且有些提醒你必须注意。
首先,忘记Thread.stop 方法吧。虽然他确实可以停掉一个正在运行的线程,但是这个方法是不安全的(unsafe)且是被弃用的(deprecated)。这意味着在将来的Java版本中,这个方法将不可用。
另一个容易迷惑的方法是Thread.interrupt。尽管名字是中止,但这个方法不会终止一个正在运行的线程(后面还会分析)。例1描述这个现象。这个例子中创建了一个线程,然后尝试使用Thread.interrupt去停止线程。例子中Thread.sleep留出充足时间供线程初始化和终止。线程本身不做任何有用的事情。
例1: Stopping a thread with 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 ) { } } } }如果你运行例1的代码,你会在终端上看到下面的东西:
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()被调用后,线程仍然会继续运行一段时间。
真正地中止一个线程
最好的,也是推荐的中止线程的方式是使用一个共享变量去通知线程停掉正在做的事情。线程必须周期性地检测这个变量,特别是执行时间较长操作时,然后有序地停掉当前的任务。例2给出了这种技术。
例2: Signaling a thread to stop 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..." ); } }
运行例2的代码会得到如下的输出(注意线程如何以预期的方式结束):
Starting thread...
Thread is running...
Thread is running...
Thread is running...
Asking thread to stop...
Thread exiting under request...
Stopping application...
虽然这个方法需要编写一些代码,但并不难实现,且给了线程处理任何清理工作的机会。这对任何多线程应用来说都是绝对需要的。注意一定要把共享变量设置为volatile,或者将访问他的操纵封装到synchronized代码块/方法中。
到现在为止,实现的已经很好了。但是如果线程被阻塞住等待一些事件呢?显然如果线程被阻塞,就不能检测共享变量,也就不能停掉。很多场景会导致线程被阻塞,举几个例子,如调用Object.wait(),ServerSocket.accept(),及DatagramSocket.receive()等。
他们都可以一直阻塞线程。即使设置了超时时间,等待超时过期也未必是可行的或期望的结果。所以必须使用一种可以提前退出阻塞状态的机制。
然后并不存在适用所有场景的机制,特定的技术只能用在特定的场景下。下面的篇幅中,我会给出大部分常见场景的方案。
使用Thread.interrupt()终止线程
正如例1中的描述,Thread.interrupt()方法并不能中止正在运行的线程。这个方法真正的效果是如果线程被阻塞,则抛出一个中止异常,这样线程退出阻塞状态。更确切的说,如果被阻塞在Object.wait,Thread.join或者Thread.sleep其中一个方法上,线程会收到一个InterruptedException异常,这样提前结束阻塞线程的方法。
所以,如果线程被上述的某个方法阻塞住,正确的终止方法是先设置共享变量,然后调用interrupt()方法(注意一定要先设置变量)。如果线程没有被阻塞,则调用interrupt()没有坏处;否则线程会获取一个异常(线程必须准备好处理这个场景)并推出阻塞状态。无论哪种情况下,最后线程会检测共享变量并终止。例3是描述这种技术的一个简单例子。
例3: Exiting blocked states with 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..." ); } }
例3中,一旦Thread.interrupt()被调用,线程就会捕获一个异常,这样他就退出阻塞状态并判断出需要结束。运行这段代码会得到如下输出:
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可能阻塞一个线程很长一段时间,特别是涉及网络通讯。例如,一个server在等待请求,或者一个网络应用在等待远端服务器的应答。
如果你在使用Java 1.4中引入的新I/O API,被阻塞的线程会得到一个ClosedByInterruptException异常。这种情况下,逻辑上与第三个例子相同,只是异常不同。
由于新I/O最近才引入且需要更多的工作,你可能还在使用Java 1.0引入的传统I/O。这种情况下,Thread.interrupt()没有效果,所以线程不会退出阻塞状态。例4说明了这种行为。虽然interrupt()被调用,但线程没有退出阻塞状态。
例4: Interrupting I/O operations with Thread.interrupt() 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..." ); } } } }
幸运的是,Java平台为这种场景提供了一个方案,即调用阻塞线程的socket的close()方法。这样线程会得到一个SocketExcpetion异常,类似于interrupt()导致抛出InterruptedException()。
唯一要注意的是socket的引用必须可以获取,以调用close()方法。也意味着socket对象必须也是共享的。例5描述了这种场景,逻辑跟目前为止给出的例子一样。
例5: Causing I/O operations to fail 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..." ); } }
这是运行例5的期望输出:
Starting thread...
Waiting for connection...
Asking thread to stop...
accept() failed or interrupted...
Thread exiting under request...
Stopping application...
多线程是个强大的工具,但也带来了自己的挑战。其中一个就是如果中止正在运行的线程。如果合理的实现,这些技术可以使得中止一个线程和使用Java平台已经提供的内置操作一样简单。
原文:http://www.techrepublic.com/article/interrupting-java-threads/5144546