`

[转]JAVA 中止线程

阅读更多
使用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描述了这种场景,逻辑跟目前为止给出的例子一样。


[java] view plaincopy
例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平台已经提供的内置操作一样简单。
分享到:
评论

相关推荐

    双线程JAVA小程序

    创建两个线程,每个线程的工作是在自己界面的TextField区域中从左到右动态地显示一个字符串;可通过界面按钮启动和中止每个线程的运行。GUI显示界面可参考下图。

    Java多线程之中断线程(Interrupt)的使用详解

    interrupt字面上是中断的意思,但在Java里Thread.interrupt()方法实际上通过某种方式通知线程,并不会直接中止该线程

    java7源码-thread:多线程相关的学习

    正确的线程中止-interrupt 如果目标线程在调用Object class的wait()、wait(long)或wait(long millis, int nanos)方法或wait(long millis, int nanos)方法、join()、join(long millis, int nanos)或s

    JAVA精确定时器

    JAVA精确定时器,利用系统时间,使长期工作的误差稳定。 功能: ·可定时启动任务或直接启动任务 ·重复启动任务(时间间隔可在任务线程中改变,范围大于100ms,否则精度降低) 引用列表: ·import psn.razerpen....

    Java并发编程示例(三):线程中断

    主要介绍了Java并发编程示例(三):线程中断,在本节,我们所开发的示例程序将会创建一个线程,五秒钟后,利用中断机制强制中止这个线程,需要的朋友可以参考下

    并发编程笔记20190526.docx

    2. 线程的启动与中止 7 3. 对Java里的线程再多一点点认识 8 三、线程间的共享和协作 9 1. 线程间的共享 9 2. ThreadLocal辨析 11 3. 线程间的协作 19 4. 面试题 21 第二章 线程的并发工具类 21 一、 Fork/Join框架的...

    MUD-28:一个使用 Java RMI 的简单多用户地牢游戏

    使用addShutDownHook()附加到客户端进程的线程处理用户中止。 这个线程在Client.java被定义为一个内部类。 处理用户离开/加入服务器或 MUD,使用MUDServerMainline的shutdownhook MUDServerMainline和服务器端方法...

    多JVM处理......

    对于多线程,您需要考虑线程安全,并且它可能会意外崩溃(例如OutOfMemoryException)并且所有线程都中止。如果您使用 jvmpart 进行处理,则一个崩溃永远不会影响其他崩溃,所有 JVM 进程都是独立运行的,并且与它...

    突破程序员基本功的16课.part2

    5.5.2 中止行注释的转义字符 5.6 泛型可能引起的错误 5.6.1 原始类型变量的赋值 5.6.2 原始类型带来的擦除 5.6.3 创建泛型数组的陷阱 5.7 正则表达式的陷阱 5.8 多线程的陷阱 5.8.1 不要调用run方法 5.8.2 ...

    jar畅游Linux后台

    目录jar通常方式jar后台运行方式方式一方式二查看后台运行任务查看某端口占用的线程的pidlinux 进程查看及杀死进程附录:各种信号及其用途 Linux系统运行jar包 jar通常方式 java -jar xxx.jar 特点:当前ssh窗口被...

    InteliWebCrawler:网络智能课程爬虫

    TODO定义登录一面的刷选规则定义根据页面内容进行刷选的正则规则使用java.util.concurrent.Executors 保证运行的thread数目不减少,尤其是parserThread异常中止后能重启。(abandoned)保存已经读取的url到文件,...

    pubmed_pagerank

    pubmed_pa​​gerank 此版本旨在以多线程方式运行一组关键字的 pagerank 计算。 运行 jar 时执行的主程序是 ProcessPaperPageRank 文件中的主程序。 该 main 有一个可选参数: 包含 pagerank.properties 文件的目录...

    DBKING使用指南

    dbking是一款基于Java的数据库处理工具集,主要包括三项主要内容: 1、为各类数据库提供统一的操作接口,尽最大可能保障各类数据库之间的移植性 2、通过生成器创建BO类及DAO层代码 3、为各类数据库之间...

    PLSQLDeveloper下载

    而且,该多线程IDE还意味着出现编程错误时不会中止:您在任何时间都可以中断执行或保存您的工作。 易于安装——不同于SQL*Net,无需中间件,也无需数据库对象安装。只需点击安装程序按钮,您就可以开始安装从而使用...

Global site tag (gtag.js) - Google Analytics